Add checksum validation for archived media
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-30 11:29:40 +01:00
parent 9a8305d986
commit eeffe4c6f1
7 changed files with 406 additions and 1 deletions

View File

@@ -46,6 +46,12 @@ class MonitorStorageCommand extends Command
$assetStats = $this->buildAssetStatistics();
$thresholds = $this->capacityThresholds();
$checksumConfig = $this->checksumAlertConfig();
$checksumWindowMinutes = $checksumConfig['window_minutes'];
$checksumThresholds = $checksumConfig['thresholds'];
$checksumMismatches = $checksumConfig['enabled'] && $checksumWindowMinutes > 0
? $this->checksumMismatchCounts($checksumWindowMinutes)
: [];
$alerts = [];
$snapshotTargets = [];
@@ -78,6 +84,7 @@ class MonitorStorageCommand extends Command
];
}
$targetChecksumMismatches = $checksumMismatches[$target->id] ?? 0;
$snapshotTargets[] = [
'id' => $target->id,
'key' => $target->key,
@@ -85,13 +92,35 @@ class MonitorStorageCommand extends Command
'is_hot' => (bool) $target->is_hot,
'capacity' => $capacity,
'assets' => $assets,
'checksum_mismatches' => [
'count' => $targetChecksumMismatches,
'window_minutes' => $checksumWindowMinutes,
],
];
}
if ($checksumConfig['enabled'] && $checksumWindowMinutes > 0) {
$totalMismatches = array_sum($checksumMismatches);
$checksumSeverity = $this->determineChecksumSeverity($totalMismatches, $checksumThresholds);
if ($checksumSeverity !== 'ok') {
$alerts[] = [
'type' => 'checksum_mismatch',
'severity' => $checksumSeverity,
'count' => $totalMismatches,
'window_minutes' => $checksumWindowMinutes,
];
}
}
$snapshot = [
'generated_at' => now()->toIso8601String(),
'targets' => $snapshotTargets,
'alerts' => $alerts,
'checksum' => [
'window_minutes' => $checksumWindowMinutes,
'mismatch_total' => array_sum($checksumMismatches),
],
];
$ttlMinutes = max(1, (int) config('storage-monitor.monitor.cache_minutes', 15));
@@ -191,4 +220,62 @@ class MonitorStorageCommand extends Command
return 'ok';
}
private function checksumAlertConfig(): array
{
$enabled = (bool) config('storage-monitor.checksum_validation.enabled', true);
$windowMinutes = max(0, (int) config('storage-monitor.checksum_validation.alert_window_minutes', 60));
$warning = (int) config('storage-monitor.checksum_validation.thresholds.warning', 1);
$critical = (int) config('storage-monitor.checksum_validation.thresholds.critical', 5);
if ($warning > $critical && $critical > 0) {
[$warning, $critical] = [$critical, $warning];
}
return [
'enabled' => $enabled,
'window_minutes' => $windowMinutes,
'thresholds' => [
'warning' => $warning,
'critical' => $critical,
],
];
}
private function checksumMismatchCounts(int $windowMinutes): array
{
$query = EventMediaAsset::query()
->selectRaw('media_storage_target_id, COUNT(*) as total_count')
->where('status', 'failed')
->where('meta->checksum_status', 'mismatch');
if ($windowMinutes > 0) {
$query->where('updated_at', '>=', now()->subMinutes($windowMinutes));
}
return $query->groupBy('media_storage_target_id')
->get()
->mapWithKeys(fn ($row) => [(int) $row->media_storage_target_id => (int) $row->total_count])
->all();
}
private function determineChecksumSeverity(int $count, array $thresholds): string
{
$warning = (int) ($thresholds['warning'] ?? 1);
$critical = (int) ($thresholds['critical'] ?? 5);
if ($count <= 0) {
return 'ok';
}
if ($critical > 0 && $count >= $critical) {
return 'critical';
}
if ($warning > 0 && $count >= $warning) {
return 'warning';
}
return 'ok';
}
}