From a3f821122a500ecfe39e96b03473f0bac5dac84c Mon Sep 17 00:00:00 2001 From: DevEnv nis2-agile Date: Sat, 30 May 2026 11:56:20 +0200 Subject: [PATCH] [FIX] Supply chain: coerenza risk_score + submit atomico (findings review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug #1 (semantica): submitPublicQuestionnaire scriveva risk_score=100-score (rischio) e sovrascriveva criticality, divergendo da assessSupplier (risk_score=compliance, alto=buono). Ora: risk_score=score (compliance), security_requirements_met (soglia 70) settato, criticality NON toccata (è la criticità del fornitore, non l'esito questionario). Bug #4 (atomicita): UPDATE ... WHERE status='sent' + rowCount()==0 -> 409. Due submit concorrenti con lo stesso token non completano due volte. Verificato E2E: submit 201 (score 94 -> risk_score=94, sec_req_met=1, criticality=high invariata), re-submit -> 409. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../controllers/SupplyChainController.php | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/application/controllers/SupplyChainController.php b/application/controllers/SupplyChainController.php index 1546a90..dc56062 100644 --- a/application/controllers/SupplyChainController.php +++ b/application/controllers/SupplyChainController.php @@ -261,13 +261,25 @@ class SupplyChainController extends BaseController $score = $maxScore > 0 ? (int) round($earned / $maxScore * 100) : 0; $riskLevel = $score >= 80 ? 'low' : ($score >= 60 ? 'medium' : ($score >= 40 ? 'high' : 'critical')); - Database::query( - 'UPDATE supplier_questionnaires SET status=?, answers=?, score=?, risk_level=?, completed_at=NOW() WHERE id=?', - ['completed', json_encode($clean, JSON_UNESCAPED_UNICODE), $score, $riskLevel, $q['id']] + // Completamento ATOMICO: vincola lo UPDATE a status='sent' così due submit + // concorrenti con lo stesso token non possono completare due volte (la seconda + // tocca 0 righe -> 409). Previene il doppio-submit senza transazione esplicita. + $upd = Database::query( + 'UPDATE supplier_questionnaires SET status=?, answers=?, score=?, risk_level=?, completed_at=NOW() + WHERE id=? AND status=?', + ['completed', json_encode($clean, JSON_UNESCAPED_UNICODE), $score, $riskLevel, $q['id'], 'sent'] ); + if ($upd->rowCount() === 0) { + $this->jsonError('Questionario gia compilato', 409, 'ALREADY_COMPLETED'); + } + + // Coerenza con assessSupplier: suppliers.risk_score = punteggio di COMPLIANCE + // (alto = buono), e security_requirements_met soglia 70. NON sovrascriviamo + // suppliers.criticality (è la criticità del fornitore, non l'esito del questionario). Database::query( - 'UPDATE suppliers SET risk_score=?, criticality=?, last_assessment_date=CURDATE() WHERE id=? AND organization_id=?', - [100 - $score, $riskLevel, $q['supplier_id'], $q['organization_id']] + 'UPDATE suppliers SET risk_score=?, security_requirements_met=?, last_assessment_date=CURDATE() + WHERE id=? AND organization_id=?', + [$score, $score >= 70 ? 1 : 0, $q['supplier_id'], $q['organization_id']] ); $this->jsonSuccess(['score' => $score, 'risk_level' => $riskLevel], 'Questionario inviato. Grazie.', 201);