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
@@ -230,19 +230,39 @@
return t('admin.jobs.every_sec', { n: secs }, 'every {{n}} s');
}
/**
* Number of findings the last completed run surfaced, from
* `last_outcome.extra.finding_count` (populated by `run_or_resume`
* on Completed/Paused). Returns 0 for jobs without a recoverable
* shape, jobs that haven't run yet, or runs pre-dating the field.
*/
function lastFindingCount(job: JobSummary): number {
if (!job.last_outcome || job.last_outcome.outcome !== 'ok') return 0;
const extra = job.last_outcome.extra as { finding_count?: number } | undefined;
return extra?.finding_count ?? 0;
}
function outcomeLabel(job: JobSummary): string {
if (!job.last_outcome) return t('admin.jobs.never', 'never');
if (job.last_outcome.outcome === 'ok') {
return t('admin.jobs.outcome_ok', 'ok');
// `ok` on the wire = dispatch completed. But if findings
// were surfaced, "ok" reads as "all good" to the operator,
// which is misleading — flip the label + colour to warn.
return lastFindingCount(job) > 0
? t('admin.jobs.outcome_issues', 'issues')
: t('admin.jobs.outcome_ok', 'ok');
}
return t('admin.jobs.outcome_err', 'err');
}
function outcomeClass(job: JobSummary): string {
if (!job.last_outcome) return 'jobs-panel__pill jobs-panel__pill--neutral';
return job.last_outcome.outcome === 'ok'
? 'jobs-panel__pill jobs-panel__pill--ok'
: 'jobs-panel__pill jobs-panel__pill--err';
if (job.last_outcome.outcome !== 'ok') {
return 'jobs-panel__pill jobs-panel__pill--err';
}
return lastFindingCount(job) > 0
? 'jobs-panel__pill jobs-panel__pill--paused'
: 'jobs-panel__pill jobs-panel__pill--ok';
}
function statusClass(status: RunStatus): string {
@@ -412,7 +432,23 @@
</td>
<td class="jobs-panel__muted">{cadenceLabel(job)}</td>
<td class="jobs-panel__muted">{timeAgo(job.last_run_at)}</td>
<td><span class={outcomeClass(job)}>{outcomeLabel(job)}</span></td>
<td>
<div class="jobs-panel__outcome-cell">
<span class={outcomeClass(job)}>{outcomeLabel(job)}</span>
{#if lastFindingCount(job) > 0}
{@const findings = lastFindingCount(job)}
<span
class="jobs-panel__pill jobs-panel__pill--err"
title={t(
'admin.jobs.findings_present_tooltip',
'Expand this run to see per-finding detail.'
)}
>
{t('admin.jobs.n_findings', { n: findings }, '{{n}} findings')}
</span>
{/if}
</div>
</td>
<td>
{#if isRunning(job)}
<span class="jobs-panel__pill jobs-panel__pill--running">
@@ -562,12 +598,38 @@
{run.progress.scanned}/{run.progress.total}
</span>
</div>
{:else if scanned != null}
<span
class="jobs-panel__muted"
title={t(
'admin.jobs.progress_scanned_only_tooltip',
'No total available for this run (pre-progress-bar deploy or the tenant does not report a countable subject).'
)}
>
{t(
'admin.jobs.progress_scanned_only',
{ n: scanned },
'{{n}} scanned'
)}
</span>
{:else}
<span class="jobs-panel__num">{scanned ?? '—'}</span>
<span class="jobs-panel__muted">—</span>
{/if}
</td>
<td class="jobs-panel__num">
{findingCount ?? 0}
{#if findingCount && findingCount > 0}
<span
class="jobs-panel__pill jobs-panel__pill--err"
title={t(
'admin.jobs.findings_present_tooltip',
'Expand this run to see per-finding detail.'
)}
>
{findingCount}
</span>
{:else}
<span class="jobs-panel__muted">0</span>
{/if}
</td>
<td class="jobs-panel__err-cell">
{#if run.error_message}
@@ -653,6 +715,14 @@
</thead>
<tbody>
{#each findings as f (f.id)}
{@const detail = (f.detail ?? {}) as Record<
string,
unknown
>}
{@const label =
(detail.path as string | undefined) ??
(detail.name as string | undefined) ??
null}
<tr>
<td><code>{f.kind}</code></td>
<td>
@@ -665,8 +735,23 @@
{f.severity}
</span>
</td>
<td class="jobs-panel__muted">
{f.resource_id ?? '—'}
<td>
{#if label}
<div class="jobs-panel__resource">
<code class="jobs-panel__resource-name"
>{label}</code
>
{#if f.resource_id}
<code class="jobs-panel__resource-uuid"
>{f.resource_id}</code
>
{/if}
</div>
{:else}
<code class="jobs-panel__muted"
>{f.resource_id ?? '—'}</code
>
{/if}
</td>
<td>
<code class="jobs-panel__detail"
@@ -970,6 +1055,31 @@
word-break: break-all;
}
.jobs-panel__resource {
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.jobs-panel__resource-name {
font-size: 0.85rem;
color: var(--color-text);
word-break: break-all;
}
.jobs-panel__resource-uuid {
font-size: 0.7rem;
color: var(--color-text-muted);
word-break: break-all;
}
.jobs-panel__outcome-cell {
display: flex;
gap: 0.4rem;
align-items: center;
flex-wrap: wrap;
}
.jobs-panel__link {
display: inline-flex;
align-items: center;
+5
View File
@@ -1213,6 +1213,9 @@
"col_findings": "Findings",
"progress_exact_tooltip": "{{pct}} ({{scanned}} / {{total}})",
"progress_approx_tooltip": "{{pct}} ({{scanned}} / {{total}} — approximate, backend proxy)",
"progress_scanned_only": "{{n}} scanned",
"progress_scanned_only_tooltip": "No total available for this run (pre-progress-bar deploy or the tenant does not report a countable subject).",
"findings_present_tooltip": "Expand this run to see per-finding detail.",
"col_error": "Error",
"col_kind": "Kind",
"col_severity": "Severity",
@@ -1233,6 +1236,8 @@
"every_sec": "every {{n}} s",
"outcome_ok": "ok",
"outcome_err": "err",
"outcome_issues": "issues",
"n_findings": "{{n}} findings",
"state_running": "running",
"never": "never",
"just_now": "just now",