v0.3.7-pre.008

This commit is contained in:
2026-09-02 16:56:11 +02:00
parent 6e406a6b84
commit 48d26b43f9
19 changed files with 693 additions and 30 deletions

View File

@@ -1,10 +1,11 @@
// file: crates/ksp-app-backfill-desk/src/app_state.rs
// version: 5
// version: 6
//! Shared backend state owned by the Backfill Desk Tauri application.
/// Shared Backfill Desk application state managed by Tauri.
pub(crate) struct AppState {
backfill_runs: crate::BackfillRunState,
config_management: ksp_config_lib::ConfigManagement,
logging_runtime: std::sync::Mutex<LoggingRuntimeState>,
shutdown_started: std::sync::atomic::AtomicBool,
@@ -76,6 +77,7 @@ impl crate::AppState {
"resolved Backfill Desk splash timings"
);
return std::result::Result::Ok(Self {
backfill_runs: crate::BackfillRunState::new(),
config_management,
logging_runtime: std::sync::Mutex::new(LoggingRuntimeState {
guard: logging_startup.guard,
@@ -121,7 +123,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.007-request-mapping".to_owned(),
shell_phase: "pre.008-runtime-start".to_owned(),
startup_diagnostic: runtime.startup_diagnostic.clone(),
});
}
@@ -194,6 +196,103 @@ impl crate::AppState {
return std::result::Result::Ok(preview);
}
/// Prepares one concrete Backfill runtime, installs its control handle atomically and lends execution resources before spawn.
pub(crate) fn prepare_backfill_start(&self, request: crate::BackfillStartRequestDto) -> ksp_core_lib::Result<crate::BackfillRunLaunch> {
if self.shutdown_started.load(std::sync::atomic::Ordering::Acquire) {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY,
"Backfill Desk cannot start a Backfill run while application shutdown is in progress",
));
}
let options = self.backfill_options();
let options = match options {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let job_id = self.backfill_runs.next_job_id();
let job_id = match job_id {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mapped = crate::map_backfill_request(request, &options, job_id.clone());
let mapped = match mapped {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let runtime = ksp_job_backfill_lib::BackfillJobRuntime::new(mapped);
let runtime = match runtime {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let handle = runtime.handle();
let installed = self.backfill_runs.install(job_id.clone(), handle);
if let std::result::Result::Err(error) = installed {
return std::result::Result::Err(error);
}
let transport = self.transport_runtime.as_ref();
let transport = match transport {
std::option::Option::Some(value) => value.pool(),
std::option::Option::None => {
let rollback = self.backfill_runs.rollback(&job_id);
if let std::result::Result::Err(error) = rollback {
return std::result::Result::Err(error);
}
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY,
"Backfill Desk cannot start a Backfill run without a ready HTTP Transport pool",
));
},
};
let store = self.store_startup.take_for_run();
let store = match store {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
let rollback = self.backfill_runs.rollback(&job_id);
if let std::result::Result::Err(rollback_error) = rollback {
return std::result::Result::Err(rollback_error);
}
return std::result::Result::Err(error);
},
};
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_RUN,
job_id = job_id.as_str(),
"admitted Backfill Desk run and installed control handle before async spawn"
);
return std::result::Result::Ok(crate::BackfillRunLaunch { job_id, runtime, store, transport });
}
/// Executes one already-admitted Backfill launch and restores application resources after its terminal result.
pub(crate) async fn execute_backfill_run(&self, launch: crate::BackfillRunLaunch) -> ksp_core_lib::Result<()> {
let job_id = launch.job_id.clone();
let result = launch.runtime.run(&launch.transport, &launch.store).await;
let restore = self.store_startup.restore_after_run(launch.store);
let finished = self.backfill_runs.finish(&job_id);
let cancellation_requested = match finished {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if let std::result::Result::Err(error) = restore {
return std::result::Result::Err(error);
}
return match result {
std::result::Result::Ok(snapshot) => {
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_RUN,
cancellation_requested = cancellation_requested,
contiguous_completed = snapshot.contiguous_completed(),
job_id = job_id.as_str(),
phase = snapshot.phase().code(),
"Backfill Desk run reached a terminal snapshot and released the single-run slot"
);
std::result::Result::Ok(())
},
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Marks graceful application shutdown as started and reports whether this caller won the one-shot transition.
pub(crate) fn begin_shutdown(&self) -> bool {
return self.shutdown_started.compare_exchange(false, true, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire).is_ok();

View File

@@ -0,0 +1,131 @@
// file: crates/ksp-app-backfill-desk/src/backfill_run.rs
// version: 1
//! Single-active-run admission and launch ownership for Backfill Desk.
/// Fully prepared Backfill execution moved into the Tauri async runtime after admission.
pub(crate) struct BackfillRunLaunch {
/// Backend-generated logical Job identity.
pub(crate) job_id: ksp_job_api::JobId,
/// Concrete Backfill runtime owning discovery, hydration, persistence and latest-value publication.
pub(crate) runtime: ksp_job_backfill_lib::BackfillJobRuntime,
/// Store facade temporarily borrowed from the application Store runtime for the duration of this run.
pub(crate) store: ksp_store_lib::Store,
/// Shareable HTTP Transport pool selected by the active composite profile.
pub(crate) transport: ksp_onchain_transport_lib::HttpTransportPool,
}
impl crate::BackfillRunLaunch {
/// Builds the safe acknowledgement returned immediately after the non-blocking Start admission.
#[must_use]
pub(crate) fn response(&self) -> crate::BackfillStartResponseDto {
return crate::BackfillStartResponseDto { job_id: self.job_id.as_str().to_owned(), state: ksp_job_api::JobState::Created.code().to_owned() };
}
}
/// Single-run control slot retained by the application while one Backfill runtime is active.
pub(crate) struct BackfillRunState {
active: std::sync::Mutex<std::option::Option<ActiveBackfillRun>>,
next_sequence: std::sync::atomic::AtomicU64,
}
impl crate::BackfillRunState {
/// Creates an empty single-run state for one desktop application session.
#[must_use]
pub(crate) const fn new() -> Self {
return Self {
active: std::sync::Mutex::new(std::option::Option::None),
next_sequence: std::sync::atomic::AtomicU64::new(1),
};
}
/// Allocates one bounded backend-owned Job identity without accepting caller-supplied identity material.
pub(crate) fn next_job_id(&self) -> ksp_core_lib::Result<ksp_job_api::JobId> {
let sequence = self
.next_sequence
.fetch_update(std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire, |current| return current.checked_add(1));
let sequence = match sequence {
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_INVALID,
"Backfill Desk Job identity sequence is exhausted",
));
},
};
return ksp_job_api::JobId::new(format!("backfill-desk-{sequence}"));
}
/// Installs one control handle atomically and rejects concurrent Starts while another run owns the slot.
pub(crate) fn install(&self, job_id: ksp_job_api::JobId, handle: ksp_job_backfill_lib::BackfillJobHandle) -> ksp_core_lib::Result<()> {
let active = self.active.lock();
let mut active = match active {
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,
"Backfill Desk active-run state lock is poisoned",
));
},
};
if active.is_some() {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_BACKFILL_RUN_ACTIVE,
"Backfill Desk already has an active Backfill run",
));
}
*active = std::option::Option::Some(ActiveBackfillRun { handle, job_id });
return std::result::Result::Ok(());
}
/// Clears the matching terminal run and reports whether cancellation had been requested on its retained handle.
pub(crate) fn finish(&self, job_id: &ksp_job_api::JobId) -> ksp_core_lib::Result<bool> {
let active = self.active.lock();
let mut active = match active {
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,
"Backfill Desk active-run state lock is poisoned",
));
},
};
let current = active.as_ref();
let current = match current {
std::option::Option::Some(value) if &value.job_id == job_id => value,
std::option::Option::Some(_) => {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_APP_STATE_INVALID,
"Backfill Desk terminal run does not match the active Job identity",
));
},
std::option::Option::None => {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_APP_STATE_INVALID,
"Backfill Desk terminal run has no active admission slot",
));
},
};
let cancellation_requested = current.handle.is_cancellation_requested();
*active = std::option::Option::None;
return std::result::Result::Ok(cancellation_requested);
}
/// Rolls back a just-installed admission when execution resources cannot be acquired before spawn.
pub(crate) fn rollback(&self, job_id: &ksp_job_api::JobId) -> ksp_core_lib::Result<()> {
let finished = self.finish(job_id);
return match finished {
std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
}
struct ActiveBackfillRun {
handle: ksp_job_backfill_lib::BackfillJobHandle,
job_id: ksp_job_api::JobId,
}
#[cfg(test)]
#[path = "../unit_tests/backfill_run.rs"]
mod tests;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/src/constants.rs
// version: 5
// version: 6
//! Application-owned tracing targets and domains.
@@ -23,6 +23,8 @@ pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "backfill.bootstrap";
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
/// Structured domain used by Backfill campaign admission and request mapping.
pub(crate) const TRACING_DOMAIN_REQUEST: &str = "backfill.request";
/// Structured domain used by concrete Backfill run admission and execution.
pub(crate) const TRACING_DOMAIN_RUN: &str = "backfill.run";
/// Structured domain used by the Backfill Desk shell.
pub(crate) const TRACING_DOMAIN_SHELL: &str = "backfill.shell";
/// Structured domain used by Store readiness and shutdown operations.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/src/dto_backfill.rs
// version: 1
// version: 2
//! Application-owned Backfill campaign DTOs and backend-derived request limits.
@@ -65,6 +65,17 @@ pub(crate) struct BackfillStartRequestDto {
pub(crate) scope_kind: String,
}
/// Safe immediate acknowledgement returned after one Backfill run is admitted and spawned.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_backfill_desk/dto_backfill/BackfillStartResponseDto.ts")]
pub(crate) struct BackfillStartResponseDto {
/// Backend-generated bounded Job identifier for this in-session run.
pub(crate) job_id: String,
/// Initial lifecycle state at the time Start returns to the frontend.
pub(crate) state: String,
}
/// Safe projection proving that one app request mapped to the KSP Backfill contract.
///
/// Address and signature values are intentionally reduced to presence/count metadata. This DTO is

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/src/errors.rs
// version: 6
// version: 7
//! Application-local error codes for Backfill Desk composition and desktop runtime surfaces.
@@ -12,6 +12,8 @@ pub(crate) const ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY: ksp_core_lib::ErrorC
ksp_core_lib::ErrorCode::new("backfill_desk", "backfill_composition_not_ready");
/// Backfill Desk received a campaign field or logical route that cannot map to the Backfill contract.
pub(crate) const ERROR_CODE_BACKFILL_REQUEST_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "backfill_request_invalid");
/// Backfill Desk refuses a concurrent Start while another Backfill run owns the single-run slot.
pub(crate) const ERROR_CODE_BACKFILL_RUN_ACTIVE: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "backfill_run_active");
/// Backfill Desk composite configuration is missing or references an unexpected document.
pub(crate) const ERROR_CODE_CONFIG_COMPOSITE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "config_composite_invalid");
/// Frontend logging requested an unsupported level.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/src/lib.rs
// version: 6
// version: 7
//! Tauri desktop application scaffold for controlling and inspecting KSP RAW backfill jobs.
@@ -9,6 +9,7 @@
mod app_state;
mod backfill_request;
mod backfill_run;
mod bootstrap;
mod constants;
mod dto_backfill;
@@ -32,6 +33,10 @@ pub(crate) use self::app_state::AppState;
pub(crate) use self::backfill_request::map_backfill_request;
/// Projects one validated Backfill request without returning address or signature values.
pub(crate) use self::backfill_request::project_backfill_request;
/// Fully prepared Backfill launch moved into the async runtime after single-run admission.
pub(crate) use self::backfill_run::BackfillRunLaunch;
/// Single-active-run admission state retained by the application.
pub(crate) use self::backfill_run::BackfillRunState;
/// Crate-internal Logging startup state shared by the application state.
pub(crate) use self::bootstrap::LoggingStartup;
/// Builds the Config management facade from the common KSP CLI bootstrap contract.
@@ -62,6 +67,8 @@ pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
/// Structured domain used by Backfill campaign admission and request mapping.
pub(crate) use self::constants::TRACING_DOMAIN_REQUEST;
/// Structured domain used by concrete Backfill run admission and execution.
pub(crate) use self::constants::TRACING_DOMAIN_RUN;
/// Structured domain used by the Backfill Desk shell.
pub(crate) use self::constants::TRACING_DOMAIN_SHELL;
/// Structured domain used by Store readiness and shutdown operations.
@@ -84,6 +91,8 @@ pub(crate) use self::dto_backfill::BackfillRequestLimitsDto;
pub(crate) use self::dto_backfill::BackfillRequestPreviewDto;
/// App-owned campaign request received from the frontend.
pub(crate) use self::dto_backfill::BackfillStartRequestDto;
/// Safe immediate acknowledgement returned after one Backfill run is admitted and spawned.
pub(crate) use self::dto_backfill::BackfillStartResponseDto;
/// Returns commitment codes admitted by the current Backfill contract.
pub(crate) use self::dto_backfill::backfill_commitment_codes;
/// Builds frontend-safe campaign limits from Job-owned public constants.
@@ -106,6 +115,8 @@ pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
pub(crate) use self::errors::ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY;
/// Backfill Desk received a campaign field that cannot map to the Backfill contract.
pub(crate) use self::errors::ERROR_CODE_BACKFILL_REQUEST_INVALID;
/// Backfill Desk refuses a concurrent Start while another Backfill run owns the single-run slot.
pub(crate) use self::errors::ERROR_CODE_BACKFILL_RUN_ACTIVE;
/// Backfill Desk composite configuration is missing or references an unexpected document.
pub(crate) use self::errors::ERROR_CODE_CONFIG_COMPOSITE_INVALID;
/// Frontend logging requested an unsupported level.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/src/store_runtime.rs
// version: 1
// version: 2
//! Composite-selected Store readiness and shutdown lifecycle owned by Backfill Desk.
@@ -21,6 +21,36 @@ impl StoreStartup {
options.composition_ready = options.transport_ready && options.network_coherent && options.store_ready;
}
/// Temporarily lends the ready Store facade to one admitted Backfill execution.
pub(crate) fn take_for_run(&self) -> ksp_core_lib::Result<ksp_store_lib::Store> {
let runtime = self.runtime.as_ref();
let runtime = match runtime {
std::option::Option::Some(value) if value.health_ready() => value,
_ => {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY,
"Backfill Desk cannot start a Backfill run without a ready Store",
));
},
};
return runtime.take_for_run();
}
/// Restores the Store facade after one Backfill execution reaches its terminal result.
pub(crate) fn restore_after_run(&self, store: ksp_store_lib::Store) -> ksp_core_lib::Result<()> {
let runtime = self.runtime.as_ref();
let runtime = match runtime {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_APP_STATE_INVALID,
"Backfill Desk cannot restore Store after runtime ownership disappeared",
));
},
};
return runtime.restore_after_run(store);
}
/// Closes the retained Store runtime if startup reached the physical Store-open phase.
pub(crate) async fn close(&self) -> ksp_core_lib::Result<()> {
let runtime = self.runtime.as_ref();
@@ -46,6 +76,44 @@ impl StoreRuntime {
return self.health_ready;
}
/// Temporarily removes the Store from the application slot for one single-active Backfill execution.
pub(crate) fn take_for_run(&self) -> ksp_core_lib::Result<ksp_store_lib::Store> {
let store = take_store(&self.store);
let store = match store {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return match store {
std::option::Option::Some(value) => std::result::Result::Ok(value),
std::option::Option::None => std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_BACKFILL_RUN_ACTIVE,
"Backfill Desk Store is already owned by an active Backfill run",
)),
};
}
/// Restores the Store after one terminal Backfill execution so a later campaign can be admitted.
pub(crate) fn restore_after_run(&self, store: ksp_store_lib::Store) -> ksp_core_lib::Result<()> {
let locked = self.store.lock();
let mut locked = match locked {
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,
"Backfill Desk Store runtime state lock is poisoned",
));
},
};
if locked.is_some() {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_APP_STATE_INVALID,
"Backfill Desk cannot restore Store into an occupied runtime slot",
));
}
*locked = std::option::Option::Some(store);
return std::result::Result::Ok(());
}
/// Explicitly closes the Store exactly once through its backend-neutral facade.
pub(crate) async fn close(&self) -> ksp_core_lib::Result<()> {
let store = take_store(&self.store);

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/src/tauri.rs
// version: 5
// version: 6
//! Tauri runtime assembly for the KSP Backfill desktop application.
@@ -77,6 +77,7 @@ fn configure_plugins(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<taur
fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
return builder.invoke_handler(tauri::generate_handler![
backfill_options,
backfill_start,
backfill_validate_request,
emit_frontend_log,
get_runtime_status,
@@ -164,6 +165,36 @@ fn backfill_options(state: tauri::State<'_, crate::AppState>) -> std::result::Re
};
}
#[tauri::command]
fn backfill_start(
app: tauri::AppHandle,
request: crate::BackfillStartRequestDto,
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::BackfillStartResponseDto, crate::CommandErrorDto> {
let launch = state.prepare_backfill_start(request);
let launch = match launch {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(project_command_error("backfill_start", crate::TRACING_DOMAIN_RUN, &error));
},
};
let response = launch.response();
let _run_task = tauri::async_runtime::spawn(async move {
let state = app.state::<crate::AppState>();
let executed = state.execute_backfill_run(launch).await;
if let std::result::Result::Err(error) = executed {
ksp_logging_lib::warn!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_RUN,
error_domain = error.code().domain(),
error_code = error.code().code(),
"Backfill Desk async run terminated with an error"
);
}
});
return std::result::Result::Ok(response);
}
#[tauri::command]
fn backfill_validate_request(
request: crate::BackfillStartRequestDto,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/src/transport_runtime.rs
// version: 4
// version: 5
//! Composite-selected HTTP Transport readiness and route inventory owned by Backfill Desk.
@@ -10,6 +10,12 @@ pub(crate) struct TransportRuntime {
}
impl TransportRuntime {
/// Returns a shareable clone of the Transport-owned HTTP pool for one admitted Backfill execution.
#[must_use]
pub(crate) fn pool(&self) -> ksp_onchain_transport_lib::HttpTransportPool {
return self.pool.clone();
}
/// Returns the composite-selected Transport profile identifier.
#[must_use]
pub(crate) fn profile_id(&self) -> &str {