[DB] Hardening integrità chiavi (mig.058): +4 UNIQUE +8 FK, retry-on-1062, bonifica 16 righe stale — v1.23.1

Estende l'audit mig.039 ai moduli 040-057 (non coperti).
TIER1 additivo (0 dup/0 orfani verificati su DB live):
- UNIQUE internal_audits/management_reviews (org,code), kb_uploaded_documents.qdrant_doc_uuid,
  whistleblowing_reports.anonymous_token (drop idx_token ridondante)
- retry-on-1062 in InternalAuditController/ManagementReviewController (allineati a periodic_controls)
- 6 FK: consulting_firm_id (organizations/users/kb)->consulting_firms; isms_soa.linked_control_id;
  isms_documents.linked_policy_id; management_review_decisions.capa_id (tutte SET NULL)
TIER2 bonifica (backup pre-DELETE in .backups/): -9 firm_org_assignments orfane (org 126-129,
  chiude deferred-b mig.039) -7 active_sessions scadute, +2 FK CASCADE.
FK totali 196->204. Sonda diagnostica db_integrity_probe.php. audit_logs lasciato by-design.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-06-18 07:57:25 +02:00
co-authored by Claude Opus 4.8
parent f32b6f28f5
commit 2b6c5087ad
8 changed files with 399 additions and 10 deletions
@@ -140,10 +140,11 @@ class InternalAuditController extends BaseController
$leadUserId = $this->validateUser($b['lead_auditor_user_id'] ?? null, $orgId);
$leadRoleId = $this->validateRole($b['lead_auditor_role_id'] ?? null, $orgId);
$code = $this->generateAuditCode($orgId);
$id = (int) Database::insert('internal_audits', [
// Retry-on-duplicate: generateAuditCode() è check-poi-insert non atomico (race su
// create concorrenti); la UNIQUE (organization_id, code) [mig.058] blocca i doppioni
// e qui si rigenera il codice.
$base = [
'organization_id' => $orgId,
'code' => $code,
'title' => $title,
'scope' => $this->nullableStr($b['scope'] ?? null),
'criteria' => $this->nullableStr($b['criteria'] ?? null),
@@ -154,7 +155,18 @@ class InternalAuditController extends BaseController
'lead_auditor_role_id' => $leadRoleId,
'conclusion' => $this->nullableStr($b['conclusion'] ?? null),
'created_by' => $this->getCurrentUserId(),
]);
];
$code = null; $id = 0;
for ($attempt = 0; ; $attempt++) {
$code = $this->generateAuditCode($orgId);
try {
$id = (int) Database::insert('internal_audits', ['code' => $code] + $base);
break;
} catch (\PDOException $e) {
if (($e->errorInfo[1] ?? 0) === 1062 && $attempt < 4) { continue; }
throw $e;
}
}
$seeded = $this->seedChecklist($id, $orgId);
$this->upsertCalendar($id, $orgId, $code, $title, $planned);