v0.3.7-pre.010
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-backfill-desk/frontend/main.html -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -134,11 +134,17 @@
|
||||
<div id="backfillRunStatusCard" class="card shadow-sm mt-4" hidden>
|
||||
<div class="card-header d-flex align-items-center justify-content-between gap-3 flex-wrap">
|
||||
<span class="fw-semibold">Monitoring latest-value</span>
|
||||
<button id="refreshBackfillStatus" class="btn btn-outline-primary btn-sm" type="button">
|
||||
<i class="fa-solid fa-rotate me-2" aria-hidden="true"></i>Resynchroniser
|
||||
</button>
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<button id="cancelBackfillRun" class="btn btn-outline-danger btn-sm" type="button" disabled>
|
||||
<i class="fa-solid fa-ban me-2" aria-hidden="true"></i>Annuler
|
||||
</button>
|
||||
<button id="refreshBackfillStatus" class="btn btn-outline-primary btn-sm" type="button">
|
||||
<i class="fa-solid fa-rotate me-2" aria-hidden="true"></i>Resynchroniser
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="backfillCancelFeedback" class="alert alert-secondary py-2" role="status" aria-live="polite" hidden></div>
|
||||
<dl class="row mb-0 app-runtime-list">
|
||||
<dt class="col-sm-5">Job</dt><dd id="runStatusJobId" class="col-sm-7 font-monospace">—</dd>
|
||||
<dt class="col-sm-5">Lifecycle</dt><dd id="runStatusLifecycle" class="col-sm-7">—</dd>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-backfill-desk/frontend/splash.html -->
|
||||
<!-- version: 1 -->
|
||||
<!-- version: 2 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<body>
|
||||
<div id="splash-container" style="opacity: 0;">
|
||||
<img id="splash-image" src="imgs/splash.png" alt="Chargement de KSP Backfill Desk">
|
||||
<div id="app-name">Backfill Desk</div>
|
||||
<div id="app-name">Backfill</div>
|
||||
<div id="debug-info" aria-live="off" hidden></div>
|
||||
<div id="messages-container" aria-live="polite"></div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/frontend/ts/main.ts
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
import "bootstrap";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
@@ -7,6 +7,7 @@ import "simplebar";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import type { BackfillRunStatusDto } from "./bindings/ksp_app_backfill_desk/backfill_status/BackfillRunStatusDto.ts";
|
||||
import type { BackfillCancelResponseDto } from "./bindings/ksp_app_backfill_desk/dto_backfill/BackfillCancelResponseDto.ts";
|
||||
import type { BackfillRequestPreviewDto } from "./bindings/ksp_app_backfill_desk/dto_backfill/BackfillRequestPreviewDto.ts";
|
||||
import type { BackfillStartRequestDto } from "./bindings/ksp_app_backfill_desk/dto_backfill/BackfillStartRequestDto.ts";
|
||||
import type { BackfillStartResponseDto } from "./bindings/ksp_app_backfill_desk/dto_backfill/BackfillStartResponseDto.ts";
|
||||
@@ -39,6 +40,7 @@ const scopeLabels: Record<string, string> = {
|
||||
};
|
||||
|
||||
let compositionReadyForStart = false;
|
||||
let currentRunStatus: BackfillRunStatusDto | null = null;
|
||||
let runStartAccepted = false;
|
||||
|
||||
|
||||
@@ -451,10 +453,15 @@ function bindBackfillRouteSelection(): void {
|
||||
}
|
||||
|
||||
|
||||
function renderBackfillRunStatus(status: BackfillRunStatusDto | null, source: "event" | "startup" | "start" | "user"): void {
|
||||
function renderBackfillRunStatus(status: BackfillRunStatusDto | null, source: "cancel" | "event" | "startup" | "start" | "user"): void {
|
||||
const card = document.querySelector<HTMLElement>("#backfillRunStatusCard");
|
||||
const cancelButton = document.querySelector<HTMLButtonElement>("#cancelBackfillRun");
|
||||
currentRunStatus = status;
|
||||
if (!status) {
|
||||
runStartAccepted = false;
|
||||
if (cancelButton) {
|
||||
cancelButton.disabled = true;
|
||||
}
|
||||
refreshStartButton();
|
||||
if (card) {
|
||||
card.hidden = true;
|
||||
@@ -463,6 +470,9 @@ function renderBackfillRunStatus(status: BackfillRunStatusDto | null, source: "e
|
||||
return;
|
||||
}
|
||||
runStartAccepted = status.active;
|
||||
if (cancelButton) {
|
||||
cancelButton.disabled = !status.active;
|
||||
}
|
||||
refreshStartButton();
|
||||
if (card) {
|
||||
card.hidden = false;
|
||||
@@ -499,7 +509,7 @@ function renderBackfillRunStatus(status: BackfillRunStatusDto | null, source: "e
|
||||
});
|
||||
}
|
||||
|
||||
async function syncBackfillStatus(source: "startup" | "start" | "user"): Promise<void> {
|
||||
async function syncBackfillStatus(source: "cancel" | "startup" | "start" | "user"): Promise<void> {
|
||||
frontendDebug("main", "Backfill Desk latest-value status resynchronization started", { source });
|
||||
const status = await invokeKsp<BackfillRunStatusDto | null>("main", "backfill_status");
|
||||
renderBackfillRunStatus(status, source);
|
||||
@@ -517,7 +527,40 @@ async function bindBackfillStatusMonitoring(): Promise<void> {
|
||||
void syncBackfillStatus("user").catch(() => frontendWarn("main", "Backfill Desk latest-value status refresh failed"));
|
||||
});
|
||||
}
|
||||
frontendTrace("main", "Backfill Desk latest-value monitoring listener installed");
|
||||
const cancelButton = document.querySelector<HTMLButtonElement>("#cancelBackfillRun");
|
||||
if (cancelButton) {
|
||||
cancelButton.addEventListener("click", () => {
|
||||
const status = currentRunStatus;
|
||||
if (!status || !status.active) {
|
||||
return;
|
||||
}
|
||||
cancelButton.disabled = true;
|
||||
frontendDebug("main", "Backfill Desk cooperative cancellation requested", { jobId: status.jobId, state: status.state });
|
||||
void invokeKsp<BackfillCancelResponseDto>("main", "backfill_cancel", { jobId: status.jobId })
|
||||
.then(response => {
|
||||
const feedback = document.querySelector<HTMLElement>("#backfillCancelFeedback");
|
||||
if (feedback) {
|
||||
feedback.hidden = false;
|
||||
feedback.textContent = response.accepted
|
||||
? `Annulation coopérative acceptée pour ${response.jobId}.`
|
||||
: `Aucune nouvelle annulation acceptée pour ${response.jobId} (${response.state}).`;
|
||||
}
|
||||
frontendInfo("main", "Backfill Desk cooperative cancellation response received", {
|
||||
accepted: response.accepted,
|
||||
jobId: response.jobId,
|
||||
state: response.state,
|
||||
});
|
||||
return syncBackfillStatus("cancel");
|
||||
})
|
||||
.catch(() => {
|
||||
frontendWarn("main", "Backfill Desk cooperative cancellation failed");
|
||||
if (currentRunStatus?.active) {
|
||||
cancelButton.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
frontendTrace("main", "Backfill Desk latest-value monitoring and cancellation handlers installed");
|
||||
}
|
||||
|
||||
function renderRuntimeStatus(status: ShellStatusDto): void {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/app_state.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
//! Shared backend state owned by the Backfill Desk Tauri application.
|
||||
|
||||
@@ -123,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.008-runtime-start".to_owned(),
|
||||
shell_phase: "pre.010-cancel-races".to_owned(),
|
||||
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
||||
});
|
||||
}
|
||||
@@ -277,6 +277,29 @@ impl crate::AppState {
|
||||
};
|
||||
}
|
||||
|
||||
/// Requests cooperative cancellation of one exact backend-issued Backfill Job identity.
|
||||
pub(crate) fn cancel_backfill(&self, job_id: &str) -> ksp_core_lib::Result<crate::BackfillCancelResponseDto> {
|
||||
let cancelled = self.backfill_runs.cancel(job_id);
|
||||
let cancelled = match cancelled {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_RUN,
|
||||
accepted = cancelled.accepted,
|
||||
job_id = cancelled.job_id.as_str(),
|
||||
state = cancelled.state.as_str(),
|
||||
"processed Backfill Desk cooperative cancellation request"
|
||||
);
|
||||
return std::result::Result::Ok(cancelled);
|
||||
}
|
||||
|
||||
/// Requests best-effort cooperative cancellation of the active run during application shutdown.
|
||||
pub(crate) fn cancel_active_backfill_for_shutdown(&self) -> ksp_core_lib::Result<bool> {
|
||||
return self.backfill_runs.cancel_active_for_shutdown();
|
||||
}
|
||||
|
||||
/// 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();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/backfill_run.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Single-active-run admission and launch ownership for Backfill Desk.
|
||||
|
||||
@@ -130,6 +130,91 @@ impl crate::BackfillRunState {
|
||||
return std::result::Result::Ok(cancellation_requested);
|
||||
}
|
||||
|
||||
/// Requests cooperative cancellation for the exact backend-issued active Job identity.
|
||||
pub(crate) fn cancel(&self, job_id: &str) -> ksp_core_lib::Result<crate::BackfillCancelResponseDto> {
|
||||
let active = self.active.lock();
|
||||
let 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 cancellation state lock is poisoned",
|
||||
));
|
||||
},
|
||||
};
|
||||
if let std::option::Option::Some(current) = active.as_ref() {
|
||||
if current.job_id.as_str() != job_id {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_BACKFILL_RUN_MISMATCH,
|
||||
"Backfill Desk cancellation target does not match the active Job",
|
||||
));
|
||||
}
|
||||
let source = current.handle.snapshots();
|
||||
let notification = <ksp_job_backfill_lib::BackfillSnapshotSource as ksp_job_api::JobSnapshotSource>::current(&source);
|
||||
let state = notification.state();
|
||||
if state.is_terminal() {
|
||||
return std::result::Result::Ok(crate::BackfillCancelResponseDto {
|
||||
accepted: false,
|
||||
job_id: current.job_id.as_str().to_owned(),
|
||||
state: state.code().to_owned(),
|
||||
});
|
||||
}
|
||||
let accepted = current.handle.cancel();
|
||||
let state_code = if current.handle.is_cancellation_requested() { "cancelling" } else { state.code() };
|
||||
return std::result::Result::Ok(crate::BackfillCancelResponseDto {
|
||||
accepted,
|
||||
job_id: current.job_id.as_str().to_owned(),
|
||||
state: state_code.to_owned(),
|
||||
});
|
||||
}
|
||||
drop(active);
|
||||
let terminal = self.last_terminal.lock();
|
||||
let terminal = match terminal {
|
||||
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 terminal cancellation state lock is poisoned",
|
||||
));
|
||||
},
|
||||
};
|
||||
if let std::option::Option::Some(notification) = terminal.as_ref() {
|
||||
if notification.id().as_str() != job_id {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_BACKFILL_RUN_MISMATCH,
|
||||
"Backfill Desk cancellation target does not match the retained terminal Job",
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(crate::BackfillCancelResponseDto {
|
||||
accepted: false,
|
||||
job_id: notification.id().as_str().to_owned(),
|
||||
state: notification.state().code().to_owned(),
|
||||
});
|
||||
}
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_BACKFILL_RUN_NOT_ACTIVE,
|
||||
"Backfill Desk has no active Backfill run to cancel",
|
||||
));
|
||||
}
|
||||
|
||||
/// Requests best-effort cooperative cancellation of whichever run is active after shutdown wins admission.
|
||||
pub(crate) fn cancel_active_for_shutdown(&self) -> ksp_core_lib::Result<bool> {
|
||||
let active = self.active.lock();
|
||||
let 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 shutdown cancellation state lock is poisoned",
|
||||
));
|
||||
},
|
||||
};
|
||||
return match active.as_ref() {
|
||||
std::option::Option::Some(current) => std::result::Result::Ok(current.handle.cancel()),
|
||||
std::option::Option::None => std::result::Result::Ok(false),
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the complete current active snapshot or the retained latest terminal snapshot for resynchronization.
|
||||
pub(crate) fn current_notification(
|
||||
&self,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/dto_backfill.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Application-owned Backfill campaign DTOs and backend-derived request limits.
|
||||
|
||||
@@ -76,6 +76,19 @@ pub(crate) struct BackfillStartResponseDto {
|
||||
pub(crate) state: String,
|
||||
}
|
||||
|
||||
/// Safe acknowledgement of one targeted cooperative cancellation request.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_backfill_desk/dto_backfill/BackfillCancelResponseDto.ts")]
|
||||
pub(crate) struct BackfillCancelResponseDto {
|
||||
/// Whether this call won the first pre-terminal cancellation request for the targeted run.
|
||||
pub(crate) accepted: bool,
|
||||
/// Backend-generated Job identifier that the cancellation request targeted.
|
||||
pub(crate) job_id: String,
|
||||
/// Safe current or cancellation-intent lifecycle code at command completion.
|
||||
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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/errors.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
//! Application-local error codes for Backfill Desk composition and desktop runtime surfaces.
|
||||
|
||||
@@ -14,6 +14,10 @@ pub(crate) const ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY: ksp_core_lib::ErrorC
|
||||
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 received a stale or mismatched Job identity for an active-run control operation.
|
||||
pub(crate) const ERROR_CODE_BACKFILL_RUN_MISMATCH: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "backfill_run_mismatch");
|
||||
/// Backfill Desk has no active or retained matching run for the requested control operation.
|
||||
pub(crate) const ERROR_CODE_BACKFILL_RUN_NOT_ACTIVE: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "backfill_run_not_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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/lib.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! Tauri desktop application scaffold for controlling and inspecting KSP RAW backfill jobs.
|
||||
|
||||
@@ -92,6 +92,8 @@ pub(crate) use self::constants::TRACING_TARGET_FRONTEND;
|
||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN;
|
||||
/// Owning target for splash-window frontend events.
|
||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
|
||||
/// Safe acknowledgement returned by targeted cooperative cancellation.
|
||||
pub(crate) use self::dto_backfill::BackfillCancelResponseDto;
|
||||
/// Backend-derived request bounds and Desk defaults for the campaign form.
|
||||
pub(crate) use self::dto_backfill::BackfillRequestLimitsDto;
|
||||
/// Safe request preview produced after strict backend mapping.
|
||||
@@ -124,6 +126,10 @@ pub(crate) use self::errors::ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY;
|
||||
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 rejects stale or mismatched Job identity control requests.
|
||||
pub(crate) use self::errors::ERROR_CODE_BACKFILL_RUN_MISMATCH;
|
||||
/// Backfill Desk has no active or retained matching run for the requested control operation.
|
||||
pub(crate) use self::errors::ERROR_CODE_BACKFILL_RUN_NOT_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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/tauri.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
//! Tauri runtime assembly for the KSP Backfill desktop application.
|
||||
|
||||
@@ -77,6 +77,7 @@ fn configure_plugins(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<taur
|
||||
#[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![
|
||||
backfill_cancel,
|
||||
backfill_options,
|
||||
backfill_start,
|
||||
backfill_status,
|
||||
@@ -114,11 +115,26 @@ fn configure_window_events(builder: tauri::Builder<tauri::Wry>) -> tauri::Builde
|
||||
if !state.begin_shutdown() {
|
||||
return;
|
||||
}
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||
"Backfill Desk main-window close requested; starting bounded Store shutdown"
|
||||
);
|
||||
let cancellation = state.cancel_active_backfill_for_shutdown();
|
||||
match cancellation {
|
||||
std::result::Result::Ok(accepted) => {
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||
cancellation_accepted = accepted,
|
||||
"Backfill Desk main-window close requested; cooperative Backfill cancellation attempted before bounded Store shutdown"
|
||||
);
|
||||
},
|
||||
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(),
|
||||
"Backfill Desk could not request cooperative cancellation during shutdown; continuing bounded shutdown"
|
||||
);
|
||||
},
|
||||
}
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let state = app_handle.state::<crate::AppState>();
|
||||
let closed = state.close_store().await;
|
||||
@@ -158,6 +174,15 @@ fn project_command_error(command: &'static str, domain: &'static str, error: &ks
|
||||
return crate::CommandErrorDto::from_error(error);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn backfill_cancel(job_id: String, state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::BackfillCancelResponseDto, crate::CommandErrorDto> {
|
||||
let result = state.cancel_backfill(job_id.as_str());
|
||||
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("backfill_cancel", crate::TRACING_DOMAIN_RUN, &error)),
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn backfill_options(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::BackfillDeskOptionsDto, crate::CommandErrorDto> {
|
||||
let result = state.backfill_options();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/tests/desktop_contract.rs
|
||||
// version: 11
|
||||
// version: 12
|
||||
|
||||
//! Structural desktop contract checks for the Backfill Desk scaffold.
|
||||
|
||||
@@ -346,3 +346,31 @@ fn pre_009_latest_value_monitoring_uses_snapshot_source_event_bridge_and_resynch
|
||||
assert!(frontend.contains("syncBackfillStatus"));
|
||||
assert!(!frontend.contains("parseBackfillLog"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_010_cancel_is_targeted_idempotent_and_shutdown_requests_cooperative_cancellation_before_exit() {
|
||||
let root = app_root();
|
||||
let state = read_text(root.join("src/app_state.rs").as_path());
|
||||
let run = read_text(root.join("src/backfill_run.rs").as_path());
|
||||
let tauri = read_text(root.join("src/tauri.rs").as_path());
|
||||
let html = read_text(root.join("frontend/main.html").as_path());
|
||||
let frontend = read_text(root.join("frontend/ts/main.ts").as_path());
|
||||
assert!(run.contains("current.job_id.as_str() != job_id"));
|
||||
assert!(run.contains("current.handle.cancel()"));
|
||||
assert!(run.contains("ERROR_CODE_BACKFILL_RUN_MISMATCH"));
|
||||
assert!(state.contains("cancel_backfill"));
|
||||
assert!(state.contains("cancel_active_backfill_for_shutdown"));
|
||||
assert!(tauri.contains("backfill_cancel"));
|
||||
assert!(tauri.contains("cancel_active_backfill_for_shutdown"));
|
||||
let shutdown_cancel = tauri.find("cancel_active_backfill_for_shutdown");
|
||||
let shutdown_exit = tauri.find("app_handle.exit(0)");
|
||||
assert!(shutdown_cancel.is_some());
|
||||
assert!(shutdown_exit.is_some());
|
||||
if let (std::option::Option::Some(cancel), std::option::Option::Some(exit)) = (shutdown_cancel, shutdown_exit) {
|
||||
assert!(cancel < exit);
|
||||
}
|
||||
assert!(html.contains("cancelBackfillRun"));
|
||||
assert!(html.contains("Annuler"));
|
||||
assert!(frontend.contains("backfill_cancel"));
|
||||
assert!(frontend.contains("{ jobId: status.jobId }"));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/tests/desktop_security.rs
|
||||
// version: 12
|
||||
// version: 13
|
||||
|
||||
//! Security and dependency-boundary checks for the Backfill Desk scaffold.
|
||||
|
||||
@@ -104,7 +104,7 @@ fn pre_002_tauri_commands_remain_centralized() {
|
||||
assert_eq!(count, 0, "{} declares a Tauri command outside tauri.rs", path.display());
|
||||
}
|
||||
}
|
||||
assert_eq!(command_count, 7);
|
||||
assert_eq!(command_count, 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -262,3 +262,36 @@ fn pre_009_monitoring_projection_exposes_counters_and_codes_without_checkpoint_o
|
||||
assert!(!dto.contains(forbidden), "monitoring DTO leaks forbidden field marker {forbidden}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_010_cancel_surface_targets_backend_job_identity_without_business_payloads() {
|
||||
let root = app_root();
|
||||
let dto_source = read_text(root.join("src/dto_backfill.rs").as_path());
|
||||
let dto = struct_source(dto_source.as_str(), "BackfillCancelResponseDto");
|
||||
for required in ["accepted", "job_id", "state"] {
|
||||
assert!(dto.contains(required), "Cancel acknowledgement missing safe field {required}");
|
||||
}
|
||||
for forbidden in [
|
||||
"pub(crate) address:",
|
||||
"pub(crate) signature:",
|
||||
"pub(crate) endpoint:",
|
||||
"pub(crate) provider:",
|
||||
"pub(crate) credential:",
|
||||
"pub(crate) token:",
|
||||
"pub(crate) checkpoint:",
|
||||
"pub(crate) payload:",
|
||||
] {
|
||||
assert!(!dto.contains(forbidden), "Cancel acknowledgement leaks forbidden field marker {forbidden}");
|
||||
}
|
||||
let frontend = read_text(root.join("frontend/ts/main.ts").as_path());
|
||||
let cancel_marker = frontend.find("Backfill Desk cooperative cancellation requested");
|
||||
assert!(cancel_marker.is_some());
|
||||
let cancel_source = match cancel_marker {
|
||||
std::option::Option::Some(index) => &frontend[index..std::cmp::min(index + 700, frontend.len())],
|
||||
std::option::Option::None => "",
|
||||
};
|
||||
assert!(cancel_source.contains("jobId: status.jobId"));
|
||||
for forbidden in ["address:", "anchorSignature:", "explicitSignatures:", "minContextSlot:"] {
|
||||
assert!(!cancel_source.contains(forbidden), "Cancel tracing includes forbidden campaign payload marker {forbidden}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/unit_tests/backfill_run.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#[test]
|
||||
fn generated_run_ids_are_backend_owned_bounded_and_unique_in_session() {
|
||||
@@ -22,3 +22,66 @@ fn generated_run_ids_are_backend_owned_bounded_and_unique_in_session() {
|
||||
assert!(first.as_str().len() <= ksp_job_api::MAX_JOB_ID_BYTES);
|
||||
assert!(second.as_str().len() <= ksp_job_api::MAX_JOB_ID_BYTES);
|
||||
}
|
||||
|
||||
fn cancellable_runtime(job_id: &str) -> std::option::Option<(ksp_job_api::JobId, ksp_job_backfill_lib::BackfillJobRuntime)> {
|
||||
let job_id = match ksp_job_api::JobId::new(job_id) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
let network = match ksp_store_lib::RawNetworkId::new("mainnet-beta") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
let request = ksp_job_backfill_lib::BackfillRequest::new(
|
||||
job_id.clone(),
|
||||
network,
|
||||
ksp_onchain_transport_lib::HttpRoleName::new("backfill_pool"),
|
||||
ksp_job_backfill_lib::BackfillCommitment::Finalized,
|
||||
ksp_job_backfill_lib::BackfillScope::latest_address(ksp_core_lib::Pubkey::new_from_array([1_u8; 32])),
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
std::option::Option::None,
|
||||
);
|
||||
let request = match request {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
let runtime = match ksp_job_backfill_lib::BackfillJobRuntime::new(request) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
return std::option::Option::Some((job_id, runtime));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn targeted_cancel_is_idempotent_and_rejects_stale_job_identity() {
|
||||
let state = crate::BackfillRunState::new();
|
||||
let runtime = cancellable_runtime("backfill-desk-41");
|
||||
assert!(runtime.is_some());
|
||||
let (job_id, runtime) = match runtime {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let installed = state.install(job_id.clone(), runtime.handle());
|
||||
assert!(installed.is_ok());
|
||||
let first = state.cancel(job_id.as_str());
|
||||
assert!(first.is_ok());
|
||||
if let std::result::Result::Ok(first) = first {
|
||||
assert!(first.accepted);
|
||||
assert_eq!(first.state, "cancelling");
|
||||
}
|
||||
let repeated = state.cancel(job_id.as_str());
|
||||
assert!(repeated.is_ok());
|
||||
if let std::result::Result::Ok(repeated) = repeated {
|
||||
assert!(!repeated.accepted);
|
||||
assert_eq!(repeated.state, "cancelling");
|
||||
}
|
||||
let stale = state.cancel("backfill-desk-40");
|
||||
assert!(stale.is_err());
|
||||
if let std::result::Result::Err(error) = stale {
|
||||
assert_eq!(error.code().domain(), crate::ERROR_CODE_BACKFILL_RUN_MISMATCH.domain());
|
||||
assert_eq!(error.code().code(), crate::ERROR_CODE_BACKFILL_RUN_MISMATCH.code());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/unit_tests/dto_backfill.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
#[test]
|
||||
fn request_limits_are_derived_from_job_owned_public_bounds() {
|
||||
@@ -43,3 +43,18 @@ fn start_acknowledgement_contains_only_backend_job_identity_and_initial_state()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancel_acknowledgement_contains_only_backend_job_identity_acceptance_and_state() {
|
||||
let response = crate::BackfillCancelResponseDto { accepted: true, job_id: "backfill-desk-1".to_owned(), state: "cancelling".to_owned() };
|
||||
let serialized = serde_json::to_string(&response);
|
||||
assert!(serialized.is_ok());
|
||||
if let std::result::Result::Ok(serialized) = serialized {
|
||||
assert!(serialized.contains("backfill-desk-1"));
|
||||
assert!(serialized.contains("cancelling"));
|
||||
assert!(serialized.contains("accepted"));
|
||||
for forbidden in ["address", "signature", "provider", "endpoint", "credential", "token", "checkpoint", "payload"] {
|
||||
assert!(!serialized.contains(forbidden));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-config-desk/frontend/splash.html -->
|
||||
<!-- version: 3 -->
|
||||
<!-- version: 4 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<body>
|
||||
<div id="splash-container" style="opacity: 0;">
|
||||
<img id="splash-image" src="imgs/splash.png" alt="Chargement de KSP Config Desk">
|
||||
<div id="app-name">Config Desk</div>
|
||||
<div id="app-name">Config</div>
|
||||
<div id="debug-info" aria-live="off" hidden></div>
|
||||
<div id="messages-container" aria-live="polite"></div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-solprices-desk/frontend/splash.html -->
|
||||
<!-- version: 2 -->
|
||||
<!-- version: 3 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<body>
|
||||
<div id="splash-container" style="opacity: 0;">
|
||||
<img id="splash-image" src="imgs/splash.png" alt="Chargement de KSP SOL Prices Desk">
|
||||
<div id="app-name">SOL Prices Desk</div>
|
||||
<div id="app-name">SOL Prices</div>
|
||||
<div id="debug-info" aria-live="off" hidden></div>
|
||||
<div id="messages-container" aria-live="polite"></div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-wallet-desk/frontend/splash.html -->
|
||||
<!-- version: 1 -->
|
||||
<!-- version: 2 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<body>
|
||||
<div id="splash-container" style="opacity: 0;">
|
||||
<img id="splash-image" src="imgs/splash.png" alt="Chargement de KSP Wallet Desk">
|
||||
<div id="app-name">Wallet Desk</div>
|
||||
<div id="app-name">Wallet</div>
|
||||
<div id="debug-info" aria-live="off" hidden></div>
|
||||
<div id="messages-container" aria-live="polite"></div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user