[FEAT] Integrazione analisi docs/nis2 v1.7.0 — scoring asset, tassonomia incidenti, PIR, NIST CSF, fonti certe

Fase 1 - Asset Relevance Scoring NIS2 (GV.OC-04): metodologia 0-100 a 6 criteri,
  AssetScoringService + endpoint scoringGrid/score/relevantSystems + UI assets.html + registro stampabile.
Fase 2 - Tassonomia incidenti Determina ACN 164179/2025: IS-1..4 + regime essenziale/importante (Allegati 3/4).
Fase 3 - Post-Incident Review (5-Whys) + metriche TTD/TTC/TTR + timestamp di fase.
Fase 4 - Mapping NIST CSF 2.0 (43 controlli) reference-only.
Fonti certe: registry config/nis2_sources.php + grounding AI (vieta riferimenti inventati) +
  citazioni help.js + ingest PDF normativi nella KB RAG (scripts/ingest-nis2-sources.php).
Migrazioni 020/021/022 (additive idempotenti). Fix VectorService IP Qdrant (drift .5->.3).
Analisi concorrenza Evix (docs/EVIX_ANALISI_CONCORRENZA.html, gap-driven).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-05-29 17:15:13 +02:00
co-authored by Claude Opus 4.8
parent a7a21faa82
commit 5c545ea3d0
41 changed files with 21078 additions and 24 deletions
+107 -1
View File
@@ -466,6 +466,7 @@
<th>Tipo</th>
<th>Categoria</th>
<th>Criticita'</th>
<th>Rilevanza NIS2</th>
<th>Owner</th>
<th>Stato</th>
<th>Azioni</th>
@@ -482,9 +483,13 @@
<td>${typeLabels[asset.asset_type] || asset.asset_type || '-'}</td>
<td>${escapeHtml(asset.category || '-')}</td>
<td><span class="criticality-badge ${crit}">${criticalityLabels[crit] || crit}</span></td>
<td>${relevanceBadge(asset)}</td>
<td>${escapeHtml(asset.owner_name || '-')}</td>
<td><span class="status-badge ${st}">${statusLabels[st] || st}</span></td>
<td>
<td style="white-space:nowrap;">
<button class="btn-icon" onclick="event.stopPropagation(); showScoringModal(${asset.id})" title="Valuta rilevanza NIS2">
<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M3 3a1 1 0 011 1v12h12a1 1 0 110 2H3a1 1 0 01-1-1V4a1 1 0 011-1zm14.707 4.707a1 1 0 00-1.414-1.414L12 10.586 9.707 8.293a1 1 0 00-1.414 0L4.586 12 6 13.414l2.293-2.293L10.586 13l5.121-5.293z" clip-rule="evenodd"/></svg>
</button>
<button class="btn-icon" onclick="event.stopPropagation(); showEditAssetModal(${asset.id})" title="Modifica">
<svg viewBox="0 0 20 20" fill="currentColor"><path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z"/></svg>
</button>
@@ -496,6 +501,106 @@
container.innerHTML = html;
}
// ── Rilevanza NIS2 (GV.OC-04) ───────────────────────────
const relevanceClassColors = {
critico: '#dc2626', alto: '#ea580c', medio: '#ca8a04',
basso: '#2563eb', trascurabile: '#6b7280'
};
function relevanceBadge(asset) {
if (asset.relevance_score === null || asset.relevance_score === undefined || asset.relevance_score === '') {
return '<span style="color:var(--gray-400); font-size:0.8rem;">Da valutare</span>';
}
const cls = asset.relevance_class || 'trascurabile';
const color = relevanceClassColors[cls] || '#6b7280';
const rel = Number(asset.is_nis2_relevant) ? ' ✓' : '';
return `<span style="display:inline-flex;align-items:center;gap:6px;font-size:0.8rem;font-weight:600;color:${color};">
<span style="display:inline-block;min-width:30px;text-align:center;padding:2px 6px;border-radius:6px;background:${color}1a;">${asset.relevance_score}</span>
${cls.charAt(0).toUpperCase() + cls.slice(1)}${rel}</span>`;
}
let _scoringGrid = null;
async function loadScoringGrid() {
if (_scoringGrid) return _scoringGrid;
const r = await api.getScoringGrid();
if (r.success) _scoringGrid = r.data;
return _scoringGrid;
}
async function showScoringModal(id) {
try {
const [assetRes, grid] = await Promise.all([api.getAsset(id), loadScoringGrid()]);
if (!assetRes.success || !grid) { showNotification('Errore caricamento dati.', 'error'); return; }
const a = assetRes.data;
let prev = a.relevance_criteria;
if (typeof prev === 'string') { try { prev = JSON.parse(prev); } catch (e) { prev = null; } }
let body = `<p style="font-size:0.85rem;color:var(--gray-600);margin-bottom:1rem;">
Metodologia di scoring rilevanza NIS2 (requisito <strong>GV.OC-04</strong>). Soglia rilevanza: <strong>&ge;${grid.threshold} punti</strong>.
Il punteggio aggiorna automaticamente anche la criticita dell'asset.</p>`;
for (const [key, def] of Object.entries(grid.grid)) {
const sel = prev && prev[key] ? prev[key].value : '';
let opts = `<option value="">— seleziona —</option>`;
for (const [ov, od] of Object.entries(def.options)) {
opts += `<option value="${ov}" data-pts="${od.points}" ${ov === sel ? 'selected' : ''}>${od.label} (${od.points})</option>`;
}
body += `<div class="form-group" style="margin-bottom:0.75rem;">
<label class="form-label" style="font-weight:600;">${def.label} <span style="color:var(--gray-400);font-weight:400;">(max ${def.max})</span></label>
<div style="font-size:0.78rem;color:var(--gray-500);margin-bottom:4px;">${def.help}</div>
<select class="form-select score-criterion" data-key="${key}" onchange="updateScorePreview()">${opts}</select>
</div>`;
}
body += `<div id="score-preview" style="margin-top:1rem;padding:0.9rem;border-radius:10px;background:var(--gray-50);font-weight:600;text-align:center;">
Totale: <span id="score-total">0</span>/100 — <span id="score-class">—</span></div>`;
showModal(`Valuta Rilevanza NIS2 — ${escapeHtml(a.name)}`, body, {
size: 'lg',
footer: `<button class="btn btn-secondary" onclick="closeModal()">Annulla</button>
<button class="btn btn-primary" onclick="submitScoring(${id})">Calcola e Salva</button>`
});
updateScorePreview();
} catch (e) {
showNotification('Errore nell\'apertura della valutazione.', 'error');
}
}
function updateScorePreview() {
let total = 0, complete = true;
document.querySelectorAll('.score-criterion').forEach(s => {
if (!s.value) { complete = false; return; }
total += parseInt(s.selectedOptions[0].dataset.pts || '0', 10);
});
let cls = total >= 80 ? 'critico' : total >= 60 ? 'alto' : total >= 40 ? 'medio' : total >= 20 ? 'basso' : 'trascurabile';
const color = relevanceClassColors[cls];
document.getElementById('score-total').textContent = total;
const clsEl = document.getElementById('score-class');
clsEl.textContent = complete ? `${cls.charAt(0).toUpperCase() + cls.slice(1)}${total >= 40 ? ' — Rilevante NIS2 ✓' : ''}` : '(completa tutti i criteri)';
clsEl.style.color = complete ? color : 'var(--gray-400)';
}
async function submitScoring(id) {
const criteria = {};
let complete = true;
document.querySelectorAll('.score-criterion').forEach(s => {
if (!s.value) complete = false;
criteria[s.dataset.key] = s.value;
});
if (!complete) { showNotification('Compila tutti i 6 criteri.', 'warning'); return; }
try {
const r = await api.scoreAsset(id, criteria);
if (r.success) {
showNotification(`Rilevanza calcolata: ${r.data.score}/100 (${r.data.class}).`, 'success');
closeModal();
loadAssets();
} else {
showNotification(r.message || 'Errore nel calcolo.', 'error');
}
} catch (e) {
showNotification('Errore di connessione.', 'error');
}
}
// ── Asset Detail View ───────────────────────────────────
async function showAssetDetail(id) {
try {
@@ -581,6 +686,7 @@
size: 'lg',
footer: `
<button class="btn btn-secondary" onclick="closeModal()">Chiudi</button>
<button class="btn btn-secondary" onclick="closeModal(); showScoringModal(${a.id})">Valuta Rilevanza NIS2</button>
<button class="btn btn-primary" onclick="closeModal(); showEditAssetModal(${a.id})">Modifica</button>
`
});
+9 -1
View File
@@ -230,6 +230,9 @@ $actionMap = [
'POST:{id}/notification' => 'sendNotification',
'POST:{id}/finalReport' => 'sendFinalReport',
'POST:{id}/aiClassify' => 'aiClassify',
'GET:{id}/metrics' => 'metrics',
'GET:{id}/pir' => 'getPir',
'POST:{id}/pir' => 'savePir',
],
// ── PolicyController ────────────────────────────
@@ -269,10 +272,13 @@ $actionMap = [
'assets' => [
'GET:list' => 'list',
'POST:create' => 'create',
'GET:scoringGrid' => 'scoringGrid',
'GET:relevantSystems' => 'relevantSystems',
'GET:dependencyMap' => 'dependencyMap',
'GET:{id}' => 'get',
'PUT:{id}' => 'update',
'DELETE:{id}' => 'delete',
'GET:dependencyMap' => 'dependencyMap',
'POST:{id}/score' => 'score',
],
// ── AuditController ─────────────────────────────
@@ -284,7 +290,9 @@ $actionMap = [
'GET:report' => 'generateReport',
'GET:logs' => 'getAuditLogs',
'GET:iso27001Mapping' => 'getIsoMapping',
'GET:nistCsfMapping' => 'getNistCsfMapping',
'GET:executiveReport' => 'executiveReport',
'GET:relevantSystemsRegister' => 'relevantSystemsRegister',
'GET:export' => 'export',
'GET:chainVerify' => 'chainVerify',
'GET:exportCertified' => 'exportCertified',
+9
View File
@@ -212,6 +212,10 @@ class NIS2API {
sendEarlyWarning(id) { return this.post(`/incidents/${id}/early-warning`, {}); }
sendNotification(id) { return this.post(`/incidents/${id}/notification`, {}); }
sendFinalReport(id) { return this.post(`/incidents/${id}/final-report`, {}); }
aiClassifyIncident(id) { return this.post(`/incidents/${id}/aiClassify`, {}); }
getIncidentMetrics(id) { return this.get(`/incidents/${id}/metrics`); }
getIncidentPir(id) { return this.get(`/incidents/${id}/pir`); }
saveIncidentPir(id, data) { return this.post(`/incidents/${id}/pir`, data); }
// ═══════════════════════════════════════════════════════════════════
// Policies
@@ -251,6 +255,11 @@ class NIS2API {
createAsset(data) { return this.post('/assets/create', data); }
getAsset(id) { return this.get(`/assets/${id}`); }
updateAsset(id, data) { return this.put(`/assets/${id}`, data); }
deleteAsset(id) { return this.delete(`/assets/${id}`); }
// NIS2 relevance scoring (GV.OC-04)
getScoringGrid() { return this.get('/assets/scoringGrid'); }
scoreAsset(id, criteria) { return this.post(`/assets/${id}/score`, { criteria }); }
listRelevantSystems() { return this.get('/assets/relevantSystems'); }
// ═══════════════════════════════════════════════════════════════════
// Audit
+21 -9
View File
@@ -207,11 +207,13 @@ const HelpSystem = (function () {
}
],
references: [
'Art. 23.1 - Obbligo di notifica degli incidenti significativi',
'Art. 23.4 (a) - Early warning entro 24 ore',
'Art. 23.4 (b) - Notifica entro 72 ore',
'Art. 23.4 (d) - Relazione finale entro un mese',
'Art. 23.3 - Definizione di incidente significativo'
'Direttiva (UE) 2022/2555 - Art. 23.1 - Obbligo di notifica degli incidenti significativi',
'Direttiva (UE) 2022/2555 - Art. 23.4 (a) - Early warning entro 24 ore',
'Direttiva (UE) 2022/2555 - Art. 23.4 (b) - Notifica entro 72 ore',
'Direttiva (UE) 2022/2555 - Art. 23.4 (d) - Relazione finale entro un mese',
'D.Lgs. 4 settembre 2024, n. 138 - Art. 23 - Notifica degli incidenti (recepimento NIS2)',
'Determinazione ACN n. 164179 del 14/04/2025 - Classificazione incidenti significativi (Allegato 3 soggetti essenziali, Allegato 4 soggetti importanti) e tipologie IS-1/IS-2/IS-3/IS-4',
'Determinazione ACN n. 333017/2025 - Piattaforma digitale ACN per le notifiche'
]
},
@@ -381,6 +383,15 @@ const HelpSystem = (function () {
'Il livello di criticita\' influenza la valutazione dei rischi associati.'
]
},
{
heading: 'Rilevanza NIS2 (scoring 0-100)',
items: [
'Il pulsante <strong>Valuta Rilevanza NIS2</strong> applica una metodologia di scoring documentata a 6 criteri pesati: Criticita Operativa (0-25), Impatto Interruzione (0-25), Dati Trattati (0-20), Dipendenze (0-15), Esposizione (0-10), Obblighi Normativi (0-5).',
'Un sistema e considerato <strong>rilevante NIS2 quando il punteggio &ge; 40</strong>. Classi: &ge;80 Critico, 60-79 Alto, 40-59 Medio, 20-39 Basso, &lt;20 Trascurabile.',
'Il punteggio aggiorna automaticamente anche la criticita dell\'asset e alimenta il registro formale dei <strong>Sistemi Rilevanti</strong>.',
'La metodologia supporta il requisito di censimento e classificazione dei sistemi informativi e di rete rilevanti, da approvare a livello di Direzione.'
]
},
{
heading: 'Mappa delle Dipendenze',
items: [
@@ -399,10 +410,11 @@ const HelpSystem = (function () {
}
],
references: [
'Art. 21.2 (i) - Sicurezza delle risorse umane, politiche di controllo dell\'accesso e gestione degli attivi',
'Art. 21.2 (a) - Politiche di analisi dei rischi e di sicurezza dei sistemi informatici',
'Art. 21.2 (c) - Continuita\' operativa, gestione dei backup e ripristino in caso di disastro',
'Considerando 79 - Adeguatezza delle misure rispetto ai rischi per le reti e i sistemi informativi'
'Direttiva (UE) 2022/2555 - Art. 21.2 (i) - Sicurezza delle risorse umane, controllo degli accessi e gestione degli attivi',
'Direttiva (UE) 2022/2555 - Art. 21.2 (a) - Politiche di analisi dei rischi e di sicurezza dei sistemi informatici',
'Direttiva (UE) 2022/2555 - Art. 21.2 (c) - Continuita\' operativa, gestione dei backup e ripristino',
'D.Lgs. 4 settembre 2024, n. 138 - Art. 24 - Obblighi in materia di misure di gestione del rischio',
'Identificazione e classificazione dei sistemi rilevanti - metodologia di scoring 0-100 approvata dalla Direzione'
]
},
+1 -1
View File
@@ -1 +1 @@
{"version":"1.6.1","build":"20260529g","date":"2026-05-29T14:55:00+02:00","changelog":"Doc: aggiornati help.js (sezione Impostazioni con Sessioni/Preferenze/Branding/Reset/Tenant), i18n.js (chiavi IT/EN per Fasi 2-5), product knowledge AI AgileHub (card NIS2 id=914)"}
{"version":"1.7.0","build":"20260529h","date":"2026-05-29T16:30:00+02:00","changelog":"FEAT integrazione analisi docs/nis2: (1) Asset Relevance Scoring NIS2 0-100 a 6 criteri (GV.OC-04) + registro formale stampabile; (2) Tassonomia incidenti Determina ACN 164179/2025 (IS-1..4, regime essenziale/importante Allegati 3-4); (3) Post-Incident Review strutturato 5-Whys + metriche TTD/TTC/TTR; (4) Layer mapping NIST CSF 2.0 (43 controlli); (5) Fonti normative certe: registry citabile + grounding AI + citazioni help + ingest PDF normativi nella KB RAG."}