feat(recoverable-job): fix files_consistency to check blob chunk consistency

This commit is contained in:
Edouard Vanbelle
2026-07-29 22:17:15 +02:00
parent 5881968f50
commit 0f12399a48
7 changed files with 326 additions and 31 deletions
+40 -3
View File
@@ -564,14 +564,27 @@ pub async fn run_or_resume(
// Dispatch. Terminal writes to `jobs.recoverable_runs` happen
// here (NOT in the handler) so the row always ends in a state
// that matches what the handler returned.
match job.run_resumable(&*store, args, resume_cursor).await {
let outcome = job.run_resumable(&*store, args, resume_cursor).await;
// Fetch the terminal run summary so we can surface aggregate
// stats (finding_count, scanned_count) on the outer JobOutcome
// extras. The outer admin listing (`GET /api/admin/jobs`) reads
// `last_outcome.extra` — without this the UI can't badge a job
// as "has findings" without also fetching the run history.
// Called AFTER the handler returns but BEFORE the terminal write,
// so stats are the ones accumulated during the run.
let (finding_count, scanned_count) = fetch_outcome_stats(&*provider, run_id).await;
match outcome {
RunOutcome::Completed => {
log_terminal_write_err("mark_completed", run_id, store.mark_completed().await);
JobOutcome::ok_with(
0,
finding_count,
serde_json::json!({
"completed": true,
"run_id": run_id.to_string(),
"finding_count": finding_count,
"scanned_count": scanned_count,
}),
)
}
@@ -579,11 +592,13 @@ pub async fn run_or_resume(
let cursor_hex = hex::encode(&cursor);
log_terminal_write_err("mark_paused", run_id, store.mark_paused(Some(cursor)).await);
JobOutcome::ok_with(
0,
finding_count,
serde_json::json!({
"paused": true,
"run_id": run_id.to_string(),
"cursor_hex": cursor_hex,
"finding_count": finding_count,
"scanned_count": scanned_count,
}),
)
}
@@ -594,6 +609,28 @@ pub async fn run_or_resume(
}
}
/// Read `finding_count` + `scanned_count` from the just-completed
/// run's `stats`. Missing/failed → `(0, 0)` — the outer outcome
/// simply won't badge findings, which is the right fallback.
async fn fetch_outcome_stats(provider: &dyn JobStoreProvider, run_id: Uuid) -> (u64, u64) {
match provider.get_run_by_id(run_id).await {
Ok(Some(summary)) => {
let finding_count = summary
.stats
.get("finding_count")
.and_then(|v| v.as_u64())
.unwrap_or(0);
let scanned_count = summary
.stats
.get("scanned_count")
.and_then(|v| v.as_u64())
.unwrap_or(0);
(finding_count, scanned_count)
}
_ => (0, 0),
}
}
fn log_terminal_write_err(op: &str, run_id: Uuid, res: Result<(), DomainError>) {
if let Err(e) = res {
tracing::warn!(