[FIX] P1 ingestion: retry su collisione incident_code + dedup race graceful + try/catch CCM (findings review)
- ingestIncident: insert in loop (max 5) -> rigenera incident_code su collisione UNIQUE (sotto carico SIEM il random a 6 cifre poteva collidere -> 500 = alert perso). Inoltre la race su external_ref (due alert simultanei) ora ritorna 200 dedup invece di 500. - controlsMonitoring (services): UPDATE auto-stale avvolto in try/catch come la gemella in AuditController (degrada con grazia se control_evidence_auto manca). Verificato E2E: ingest 201, dedup 200. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5413730b00
commit
43c6a87c5f
@ -2369,7 +2369,40 @@ class ServicesController extends BaseController
|
||||
$data['final_report_due'] = date('Y-m-d H:i:s', $t + 30 * 86400);
|
||||
}
|
||||
|
||||
$incidentId = Database::insert('incidents', $data);
|
||||
// Insert resiliente: retry su collisione incident_code (random 6 cifre),
|
||||
// e gestione graceful della race su external_ref (dedup concorrente → 200).
|
||||
$incidentId = null;
|
||||
for ($attempt = 0; $attempt < 5; $attempt++) {
|
||||
try {
|
||||
$incidentId = Database::insert('incidents', $data);
|
||||
break;
|
||||
} catch (Throwable $e) {
|
||||
$msg = $e->getMessage();
|
||||
if (stripos($msg, 'Duplicate') === false && stripos($msg, '1062') === false) {
|
||||
throw $e; // errore non-duplicate: propaga
|
||||
}
|
||||
// Race su external_ref: un altro processo ha gia ingerito lo stesso alert
|
||||
if ($externalRef !== null && stripos($msg, 'uq_incident_external_ref') !== false) {
|
||||
$dup = Database::fetchOne(
|
||||
'SELECT id, incident_code FROM incidents WHERE organization_id = ? AND external_ref = ?',
|
||||
[$orgId, $externalRef]
|
||||
);
|
||||
if ($dup) {
|
||||
$this->jsonSuccess([
|
||||
'id' => (int) $dup['id'],
|
||||
'incident_code' => $dup['incident_code'],
|
||||
'deduplicated' => true,
|
||||
], 'Alert già ingerito (dedup concorrente)', 200);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Altrimenti: collisione su incident_code → rigenera e riprova
|
||||
$data['incident_code'] = $this->generateCode('INC');
|
||||
}
|
||||
}
|
||||
if ($incidentId === null) {
|
||||
$this->jsonError('Impossibile registrare l\'incidente (codice duplicato)', 500, 'INSERT_FAILED');
|
||||
}
|
||||
|
||||
Database::insert('incident_timeline', [
|
||||
'incident_id' => $incidentId,
|
||||
@ -2626,23 +2659,28 @@ class ServicesController extends BaseController
|
||||
$this->requireApiKey('read:compliance');
|
||||
$orgId = $this->currentOrgId;
|
||||
|
||||
// Auto-stale: controlli la cui ultima evidenza è scaduta vanno marcati stale
|
||||
Database::query(
|
||||
"UPDATE compliance_controls cc
|
||||
LEFT JOIN (
|
||||
SELECT t.control_code, t.valid_until, t.status
|
||||
FROM control_evidence_auto t
|
||||
JOIN (SELECT control_code, MAX(collected_at) mx FROM control_evidence_auto
|
||||
WHERE organization_id = ? GROUP BY control_code) m
|
||||
ON m.control_code = t.control_code AND m.mx = t.collected_at
|
||||
WHERE t.organization_id = ?
|
||||
) last ON last.control_code = cc.control_code
|
||||
SET cc.monitoring_status = 'stale'
|
||||
WHERE cc.organization_id = ?
|
||||
AND cc.monitoring_status NOT IN ('not_monitored')
|
||||
AND last.valid_until IS NOT NULL AND last.valid_until < NOW()",
|
||||
[$orgId, $orgId, $orgId]
|
||||
);
|
||||
// Auto-stale: controlli la cui ultima evidenza è scaduta vanno marcati stale.
|
||||
// Degrada con grazia se la tabella evidenze non esiste (coerente con AuditController).
|
||||
try {
|
||||
Database::query(
|
||||
"UPDATE compliance_controls cc
|
||||
LEFT JOIN (
|
||||
SELECT t.control_code, t.valid_until, t.status
|
||||
FROM control_evidence_auto t
|
||||
JOIN (SELECT control_code, MAX(collected_at) mx FROM control_evidence_auto
|
||||
WHERE organization_id = ? GROUP BY control_code) m
|
||||
ON m.control_code = t.control_code AND m.mx = t.collected_at
|
||||
WHERE t.organization_id = ?
|
||||
) last ON last.control_code = cc.control_code
|
||||
SET cc.monitoring_status = 'stale'
|
||||
WHERE cc.organization_id = ?
|
||||
AND cc.monitoring_status NOT IN ('not_monitored')
|
||||
AND last.valid_until IS NOT NULL AND last.valid_until < NOW()",
|
||||
[$orgId, $orgId, $orgId]
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
error_log('[CCM-services] ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT control_code, title, status, monitoring_status, last_checked_at, freshness_days, implementation_percentage
|
||||
|
||||
Loading…
Reference in New Issue
Block a user