diff --git a/docs/kb/RISCHI_MODELLO.md b/docs/kb/RISCHI_MODELLO.md new file mode 100644 index 0000000..345554d --- /dev/null +++ b/docs/kb/RISCHI_MODELLO.md @@ -0,0 +1,26 @@ +FONTE: interfaccia prodotto NIS2 Agile — pagina "Gestione Rischi". +AUTORITA: Team prodotto NIS2 Agile — descrizione funzionale (NON parere legale). +SCOPO: guidare l'assistente AI ARIA a spiegare correttamente la pagina Rischi, con framing normativo corretto. + +# Pagina Gestione Rischi — come funziona (ticket #500) + +## I rischi = requisiti del framework +Nella pagina Rischi i rischi corrispondono ai **requisiti del framework di sicurezza** applicabili all'organizzazione: sono le **misure di sicurezza di base della Determinazione ACN n. 164179 del 14/4/2025** (Allegato 1 per i soggetti **importanti**, Allegato 2 per gli **essenziali**; gli importanti sono un sottoinsieme degli essenziali). + +⚠️ **Formulazione obbligatoria**: un requisito NON è di per sé un rischio. Ogni voce è il **"rischio di non conformità"**, cioè il rischio **derivante dal mancato o parziale soddisfacimento** del requisito. Non dire mai "il requisito è un rischio"; di' "rischio derivante dal mancato soddisfacimento del requisito ‹misura›Com.N". + +## Codifica +Ogni rischio ha un codice **RSK-‹misura›Com.‹n›** (es. `RSK-GV.OC-04Com.1`): è una **codifica INTERNA di NIS2 Agile**. La parte ‹misura› (es. GV.OC-04) è il codice della misura del **Framework Nazionale** richiamato dalla Determinazione ACN 164179/2025; ‹Com.n› identifica il comma del requisito. **RSK-… NON è un codice ufficiale ACN.** + +## Valutazione e matrice 5×5 +Ogni rischio-requisito può essere valutato (tasto **"Valuta"**, riservato ai profili che valutano: super_admin/org_admin/compliance_manager) con **Probabilità** (1-5) e **Impatto** (1-5). Il prodotto P×I posiziona il rischio nella **matrice 5×5**, che è sempre visibile. La matrice **esclude i requisiti "non applicabili"**. +- Probabilità = probabilità che la minaccia si concretizzi sfruttando la carenza; Impatto = conseguenze della non conformità/incidente. Non è "la probabilità del requisito". +- La matrice e il metodo probabilità×impatto sono uno **strumento di rappresentazione**, NON un obbligo: l'**art. 24 del D.Lgs. 138/2024** richiede un'analisi dei rischi proporzionata e con approccio **multirischio (all-hazard)**, senza imporre una metodologia specifica. + +## Rischi aggiuntivi (all-hazard) +Oltre ai rischi derivati dai requisiti, i profili **amministrativi** possono aggiungere "Rischi aggiuntivi" non coperti da un singolo requisito (minacce fisiche, catena di fornitura, eventi naturali, errore umano). Serve a coprire l'approccio **multirischio/all-hazard** richiesto dall'art. 24. Gli utenti non amministrativi non vedono i tasti di inserimento. + +## Regole per l'assistente +- Indirizza alla pagina dal **menu in alto "Rischi" → "Mappa dei rischi"**. +- Non presentare "92" (numero requisiti importanti) né i codici RSK-… come dati/codici ufficiali ACN. +- Cita, quando serve: Determinazione ACN n. 164179/2025 (All.1 importanti, All.2 essenziali) e art. 24 D.Lgs. 138/2024. diff --git a/scripts/ingest-risks-model.php b/scripts/ingest-risks-model.php new file mode 100644 index 0000000..7674dfa --- /dev/null +++ b/scripts/ingest-risks-model.php @@ -0,0 +1,56 @@ +=$len)break; $s+=($size-$ov);} return $r; } + +if ($DOC === '') { logln('ERRORE: doc non trovato'); exit(1); } +logln("doc: $DOC"); +$text = preg_replace('/\n{3,}/',"\n\n", trim(preg_replace('/[ \t]+/',' ', (string)file_get_contents($DOC)))); +if (strlen($text) < 150) { logln('ERRORE: testo troppo breve'); exit(1); } +$chunks = chunkText("DOCUMENTO: Come funziona la pagina Rischi di NIS2 Agile (rischi=requisiti, framing corretto).\n\n".$text); +logln(strlen($text).' char -> '.count($chunks).' chunk'); + +$embed = new EmbedService(); $vector = new VectorService(); $vector->ensureCollection($embed->dims); +try { + $vector->deleteByFilter(['must'=>[['key'=>'scope','match'=>['value'=>'SYSTEM']],['key'=>'source','match'=>['value'=>$SOURCE]]]]); + logln('rimossi chunk precedenti di questa fonte'); +} catch (Exception $e) { logln('(warn) delete: '.$e->getMessage()); } + +$docUuid = uuid(); $points = []; +foreach ($chunks as $i=>$ch) { + $vec=null; for($try=1;$try<=5;$try++){ try{ $vec=$embed->embed($ch); break; }catch(Throwable $e){ if($try===5) throw $e; sleep($try);} } + $points[]=['id'=>uuid(),'vector'=>$vec,'payload'=>['doc_uuid'=>$docUuid,'title'=>$TITLE.($i>0?' (parte '.($i+1).')':''),'chunk'=>$ch,'entity_type'=>'guida_prodotto','source'=>$SOURCE,'lang'=>'it','scope'=>'SYSTEM','consulting_firm_id'=>null,'organization_id'=>null,'shared_with_orgs'=>[],'uploaded_by'=>0]]; +} +foreach (array_chunk($points,64) as $b) { $vector->upsertBatch($b); } +try { + $stmt = Database::getInstance()->prepare("INSERT INTO kb_uploaded_documents (qdrant_doc_uuid, scope, consulting_firm_id, organization_id, uploaded_by, title, entity_type, source, lang, chunk_count, shared_with_orgs, status) VALUES (?, 'SYSTEM', NULL, NULL, 0, ?, 'guida_prodotto', ?, 'it', ?, '[]', 'ready')"); + $stmt->execute([$docUuid, $TITLE, $SOURCE, count($chunks)]); +} catch (Exception $e) { logln('(warn) tracking: '.$e->getMessage()); } +logln("OK indicizzato: $docUuid, ".count($chunks).' chunk');