Commit Graph
82 Commits
Author SHA1 Message Date
DevEnv nis2-agileandClaude Opus 4.8 4809d8e895 [FEAT] Pagina Rischi (Simon): matrice a 4 fasce + toggle inerente/residuo; trasversali 12 col in fondo
Richieste Simon sulla finestra Rischi:
1) Matrice 5×5: eliminata la fascia 10-14; fasce = 1-2 basso (verde), 3-8 medio
   (arancione), 9-12 alto (rosso tenue), 15-25 critico (rosso). Unificate con le
   bande Alert. (matrixBand + CSS band-low/med/high/crit; rimosse level-1..10).
2) La matrice riepiloga i giudizi INERENTI o RESIDUI con un tasto di selezione
   (setMatrixView; matrixPoints legge likelihood/impact o residual_*).
3) Rischi trasversali:
   3.1 sezione spostata in FONDO (matrice dai requisiti risalita subito dopo la
       tabella derivata);
   3.2 tabella trasversali convertita alle STESSE 12 colonne (inerente+residuo+
       alert+esito), editing inline P/I via api.updateRisk (onTransversalScoreChange);
   3.3 tasti "AI Suggerisci" e "Nuovo Rischio" spostati in testa alla tabella;
   3.4 seconda matrice 5×5 dei trasversali con lo stesso toggle inerente/residuo;
   3.5 tasto Elimina per ogni rischio trasversale (confirmDeleteRisk).
- Solo frontend/statico. version 1.27.0 + SW cache bump.
- Verificato puppeteer (996002): 4 bande, 2 toggle funzionanti, 12 col, 6 delete, 0 errori.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 10:39:05 +02:00
DevEnv nis2-agileandClaude Opus 4.8 371e98c3c7 [RELEASE] v1.26.0 — Gestione Rischi 12 colonne (inerente+residuo). SW cache bump.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-30 10:59:54 +02:00
DevEnv nis2-agileandClaude Opus 4.8 cf224480cc [FEAT] Sessione + release: refresh token silenzioso + avviso "nuova versione"
Evita la "sessione scaduta" e i deploy che restano invisibili all'utente.
- api.js: refresh PROATTIVO dell'access token ~2 min prima della scadenza
  (scheduleTokenRefresh su exp del JWT, riprogrammato ad ogni setTokens) +
  ensureFreshToken() per rinnovo immediato al ritorno sulla scheda. Con refresh
  a 7gg l'utente resta loggato senza mai vedere il login.
- common.js: watch versione (poll version.json ogni 5 min + su visibilitychange)
  → banner "È disponibile una nuova versione — Aggiorna" (reload) quando cambia;
  nudge service worker (registration.update); su visibilitychange chiama anche
  api.ensureFreshToken(). Nessun hard-refresh manuale.
- Cache-buster api/common ?v=20260729b. version 1.25.12. Solo frontend.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 16:07:57 +02:00
DevEnv nis2-agileandClaude Opus 4.8 faadb62da6 [FEAT] Ruolo consulente: visibilità firm-scoped (org-switcher + Cruscotto Studio)
Un utente con role='consultant' e consulting_firm_id vede solo i CLIENTI del
proprio studio (organizations.consulting_firm_id = suo firm) UNION le proprie
membership dirette — non tutte le org (quello resta super_admin), non solo le
membership. Applicato in OrganizationController::list (selettore azienda) e
ConsultantController::portfolio (cruscotto). Additivo: super_admin e altri ruoli
invariati. Verificato: utente-test consultant/firm1 vede 2 aziende (cliente studio
996003 + membership 996001), non tutte. version 1.25.11.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 15:53:54 +02:00
DevEnv nis2-agileandClaude Opus 4.8 a8516efafc [FIX] Gestione Rischi #501: tabella requisiti a scorrimento verticale + filtro per classe soggetto
- Punto 1: la tabella 'Rischi dai requisiti del framework di sicurezza' e ora
  un riquadro a scorrimento verticale (max-height 60vh, header sticky) invece di
  allungare la pagina.
- Punto 2: elenca SOLO i requisiti applicabili alla classe del soggetto
  (importante -> Allegato 1, essenziale -> Allegato 2, Det. ACN 164179/2025);
  le righe non pertinenti non sono piu mostrate (prima solo attenuate).
- help.js allineato + cache-buster help.js?v=20260729b su 35 pagine; version.json 1.25.10.
- Punto 3 (rimozione sezione all-hazard) NON applicato: contraddice la decisione
  documentata nel #500 (copertura multirischio art.24 D.Lgs.138/2024) -> escalato.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 15:11:36 +02:00
DevEnv nis2-agileandClaude Opus 4.8 15010f7dc3 [FEAT] Cruscotto Studio (frontend): companies.html vista d'insieme + menu
companies.html diventa il cruscotto dello studio consulente: barra KPI (aziende,
compliance media, scadenze in ritardo/in arrivo), griglia aziende (compliance/
rischi/incidenti + badge ritardo, clic = entra nell'azienda) e sezione "Scadenze
generali" aggregata su TUTTE le aziende ordinata per data. Una sola chiamata
api.consultantPortfolio(). Voce menu V3 "Le mie aziende" (icona briefcase) gata a
consultant/super_admin. help.js + i18n (nav.studio + companies.*) aggiornati.
Cache-buster api/topnav-v3/i18n/help ?v=20260729. version 1.25.9.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 14:49:37 +02:00
DevEnv nis2-agileandClaude Opus 4.8 924963444e [FEAT] Rischi: suggerimento probabilità/impatto dallo stato di conformità
Nella modale "Valuta" di un rischio-requisito non ancora valutato, Probabilità e
Impatto vengono pre-compilati in base allo stato (conforme→basso 1/2, parziale→
medio 3/3, non conforme→alto 4/4) con nota esplicita "Valori suggeriti … è solo
un suggerimento, modificabili". Se già valutato, usa i valori salvati. Idea
pre-avallata dal nis2-expert (precompilare l'asse dallo stato = rischio residuo).
Solo frontend (risks.html); nessun valore imposto (art.24 non prescrive metodo).
version 1.25.8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 08:39:40 +02:00
DevEnv nis2-agileandClaude Opus 4.8 f6d2076684 [FEAT] Rischi #500 opzione B: matrice dai rischi-requisito (probabilità×impatto)
Ogni rischio derivato da un requisito puo' ora essere valutato con Probabilita' e
Impatto (1-5) e compare nella matrice 5x5.
- DB: mig 066 (org_requisito_state += likelihood/impact) + runner idempotente.
- Backend: FrameworkController::setState accetta likelihood/impact; RiskController
  ::derivedList ritorna likelihood/impact/score. Applicata su prod + reload.
- Frontend risks.html: colonne Probabilita'/Impatto/Rischio(P×I) + tasto "Valuta"
  (modale, riservato a super_admin/org_admin/compliance_manager); matrice calcolata
  client-side dai rischi derivati valutati (esclusi i non_applicabile, caveat
  nis2-expert) + rischi custom. Framing normativo invariato.
- help.js/i18n aggiornati; version 1.25.7; buster help/i18n ?v=20260727b.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:32:59 +02:00
DevEnv nis2-agileandClaude Opus 4.8 f1156a81bb [FIX] Rischi #500: ripristino tasti Nuovo/AI LIMITATI ai profili admin (decisione utente)
Il supervisore autonomo (b5f936b) aveva RIMOSSO del tutto i tasti 'Nuovo Rischio'
e 'AI Suggerisci' applicando il ticket alla lettera. Decisione utente (Cristiano):
NON rimuoverli ma limitarli ai profili super_admin/org_admin — l'inserimento libero
serve a coprire i rischi all-hazard non legati a un singolo requisito (art.24
D.Lgs.138/2024, confermato nis2-expert). Ripristinato il gating (gateAdminActions):
i tasti compaiono solo agli admin, nascosti agli altri. version 1.25.6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:20:44 +02:00
DevEnv nis2-agileandClaude Opus 4.8 b5f936bba2 [FEAT] Rischi #500 punto 4: rimossi tasti 'Nuovo Rischio' e 'AI Suggerisci' (elenco = requisiti Lex&ISO, sola lettura)
I rischi in Gestione Rischi corrispondono ai requisiti del framework (Determinazione ACN 164179/2025);
niente inserimento manuale/AI. Reverte l'admin-gating: la spec del richiedente e il piano originale
prevedevano la rimozione dei tasti. La sezione 'Rischi aggiuntivi' resta come elenco in sola lettura.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:13:48 +02:00
DevEnv nis2-agileandClaude Opus 4.8 4e7e75582a [FEAT] Rischi #500 fase 2 (frontend): elenco rischi = requisiti Lex&ISO
La pagina Gestione Rischi mostra come tabella primaria (read-only) i requisiti
del framework Lex&ISO come rischi, codice RSK-<misura>Com.N via /api/risks/derived,
filtrati per classe (importante All.1 / essenziale All.2). Framing normativo
(validato nis2-expert): ogni voce = "rischio derivante dal mancato/parziale
soddisfacimento del requisito", mai "requisito = rischio"; RSK e "92" NON come
codici/numeri ufficiali ACN; matrice = strumento di rappresentazione (art.24).
- Rischi CUSTOM mantenuti in sezione "Rischi aggiuntivi (all-hazard)".
- Tasti "Nuovo Rischio"/"AI Suggerisci" limitati a super_admin/org_admin (non
  rimossi): l'inserimento libero serve al multirischio (art.24 D.Lgs.138/2024).
- api.js derivedRisks(); help.js + i18n aggiornati; version 1.25.4; SW v1.25.4.
- Cache-buster api/help/i18n ?v=20260727r. Backend/DB non toccati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 14:15:03 +02:00
DevEnv nis2-agileandClaude Opus 4.8 571e151305 [FEAT] Rischi #500 fase 2 backend: endpoint /api/risks/derived (rischi dai requisiti LEX&ISO, codifica RSK-<misura>Com.N)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 13:41:11 +02:00
DevEnv nis2-agileandClaude Opus 4.8 7c711b727a [FEAT] Rischi: matrice sempre visibile, rimosse viste FAIR e KRI (ticket #500 fase 1)
Ticket #500 (Simon Fattori) - semplificazione pagina Gestione Rischi:
- matrice 5x5 ora sempre visibile sotto la tabella (rimosso bottone 'Matrice' e il view-toggle)
- rimosse le viste 'Quantitativo (FAIR)' e 'KRI' con relativi bottoni, blocchi HTML e JS
- help.js allineato (rimosse sezioni FAIR/KRI), cache-buster help.js -> 20260727a
- version.json -> 1.25.2
Rimandato a fase 2 (richiede modello dati): rischi derivati dai requisiti LEX&ISO
+ codifica RSK-<misura>Com.N + rimozione bottoni 'Nuovo Rischio'/'AI Suggerisci'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 12:15:22 +02:00
DevEnv nis2-agileandClaude Opus 4.8 896663d24c [FEAT] Dashboard: card Documenti istituzionali funzionante + UI (ticket #499)
- loadDocs: 5 voci standard sempre presenti + voci custom + 'Aggiungi voce'
- modale add/edit con descrizione OBBLIGATORIA (richiesta punto 1), link doc opzionale
- delete voci custom; controlli visibili a org_admin/compliance_manager/super_admin
- CSS .doc-* in v3.css; cache-buster dashboard.js/v3.css su dashboard.html; version 1.25.1

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 15:15:26 +02:00
DevEnv nis2-agileandClaude Opus 4.8 64e10b152b [RELEASE] v1.25.0 — UI V3: version.json + shell service worker aggiornati
- version.json 1.24.15 → 1.25.0 (MINOR: nuova interfaccia V3) con changelog
  utente; allinea footer/changelog e la versione nel bug-reporter alla UI reale.
- sw.js: cache 'nis2-shell-v1.23.0' → 'v1.25.0' + PRECACHE portato allo shell V3
  (v3.css/topnav-v3.js/inter/lucide al posto di style.css/common-bi/bootstrap-italia).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:26:36 +02:00
DevEnv nis2-agileandClaude Opus 4.8 ab3e88299e [FEAT] Inventario: ordinamento lista per colonna (#442)
Click su un'intestazione (Nome/Tipo/Categoria/Criticita/Rilevanza NIS2/Owner/Stato)
ordina l'inventario; secondo click inverte (freccia ▲/▼, aria-sort).
Criticita e Rilevanza ordinano per valore reale, non alfabetico.
Client-side sull'inventario gia' caricato (no paginazione su assets), si combina
coi filtri, accessibile da tastiera. Help aggiornato + cache-buster help.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 10:41:34 +02:00
DevEnv nis2-agileandClaude Opus 4.8 87fc5bf959 [FEAT] Inventario: esportazione CSV in formato import-compatibile (#441)
Nuovo pulsante "Esporta" in assets.html: scarica l'intero inventario
(tutte le pagine) in CSV con le stesse colonne riconosciute
dall'import (name, asset_type, criticality, data_classification,
internet_facing, dependencies_count, regulated, external_ref, vendor,
location). I criteri data_classification/internet_facing/
dependencies_count/regulated sono ricostruiti da relevance_criteria con
mappatura reversibile (round-trip stabile, verificato 100/100 casi).
Parser import reso quote-aware (RFC-style) per round-trip lossless di
nomi con virgola; retro-compatibile con CSV non quotato.
help.js + cache-buster (?v=20260625b) su tutte le pagine. version 1.24.14.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:13:22 +02:00
DevEnv nis2-agileandClaude Opus 4.8 9c36604b2b [FEAT] Inventario: Mappa Dipendenze + eliminazione beni (#440, #439)
- Nuovo pulsante "Mappa Dipendenze" in toolbar: vista delle relazioni
  tra i beni (costruita client-side da allAssets, endpoint backend gia'
  esistente dependencyMap).
- Campo "Dipendenze" (multi-select) nel form bene: consente di compilare
  la mappa che ARIA indicava ma che non aveva UI. create/update salvano
  gia' il campo dependencies.
- Pulsante "Elimina" bene nella lista e nel dettaglio (DELETE /assets/{id}
  gia' esistente, richiede org_admin).
- Dettaglio bene: dipendenze mostrate per nome anziche' per ID grezzo.
- version.json -> 1.24.13.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 08:43:54 +02:00
DevEnv nis2-agileandClaude Opus 4.8 e5f7a7d3a6 [FIX] Widget Segnalazioni: cap descrizione 1900 char + contatore, evita errore 'Data too long operative_summary' (ticket #430)
La colonna operative_summary lato ticket-ms e' VARCHAR(2000): descrizioni
lunghe causavano l'errore SQL grezzo a video. Aggiunto maxlength + contatore
live + guard al submit nel widget locale public/js/bug-reporter.js.
Cache-buster: bug-reporter.js?v=20260624 in common.js + common.js?v=20260624
su tutte le pagine. Causa radice (dimensione colonna) inoltrata ad AgileHub.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 03:11:19 +02:00
DevEnv nis2-agileandClaude Opus 4.8 08d4ac0cc4 [FIX] ARIA: inietta griglia esatta combo Rilevanza NIS2 nel prompt (ticket #429)
ARIA (askWithRag) conosceva solo il nome pagina + KB/fonti, non la griglia
reale delle tendine della finestra «Valuta Rilevanza NIS2» (AssetScoringService::GRID):
dava risposte generiche non corrispondenti alla finestra. Aggiunto
relevanceGridBlock() (6 criteri, opzioni, punteggi, desc dalla fonte di verita')
iniettato nel system prompt quando l'utente e nell'Inventario o chiede della
rilevanza. Additivo, nessun impatto su altri flussi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 00:09:24 +02:00
DevEnv nis2-agileandClaude Opus 4.8 cf23be9c77 [FEAT] Inventario Rilevanza NIS2: significato opzioni combo + dettaglio punteggio per criterio (ticket #431, #432)
- AssetScoringService::GRID: aggiunto campo 'desc' a tutte le 35 opzioni dei 6 criteri (significato operativo, additivo)
- assets.html: ogni combo mostra il significato dell'opzione selezionata + nuovo box 'Dettaglio punteggio per criterio' (live, anche alla riapertura di una valutazione salvata)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 22:43:43 +02:00
DevEnv nis2-agileandClaude Opus 4.8 ea5966eaee [FIX] ARIA: mappa navigazione reale + contesto pagina (ticket #424)
ARIA citava voci di menu inesistenti (es. 'A4 - Analisi e Valutazione
del Rischio' / 'Fase 4.1 - Inventario') non corrispondenti alla barra
laterale, e non sapeva da quale pagina scrivesse l'utente.

- AIService::askWithRag: iniettata la mappa REALE della sidebar
  (navigationMapBlock, sincronizzata con common-bi.js) con regole che
  vietano di inventare menu/fasi/codici; aggiunto contesto pagina utente.
- AiController::ask: accetta e passa il campo 'page' dal frontend.
- common.js (ARIA FAB): invia la pagina corrente in /api/ai/ask.
- Cache-buster common.js 20260623->20260623h su tutte le pagine.

NB: questo commit include anche il fix #419 gia' applicato e live in
common.js (email bug-reporter recuperata via api.getMe quando assente
nelle claims JWT), finora non committato — incluso come backup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 21:12:36 +02:00
DevEnv nis2-agileandClaude Opus 4.8 637e1ec510 [FEAT] Inventario: rinomina/rimuovi sottoclassi org dalla modifica bene (ticket #426)
Aggiunti endpoint PUT/DELETE /api/assets/subclasses/{id} (org-scoped, le
sottoclassi di sistema restano protette; FK ON DELETE SET NULL scollega i
beni senza cancellarli) + link UI «rinomina»/«rimuovi» accanto a «aggiungi».
Cache-buster api.js + bump version 1.24.8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 19:42:02 +02:00
DevEnv nis2-agileandClaude Opus 4.8 7529c59a75 [FIX] Inventario: combo Rilevanza NIS2 ripopolate alla riapertura (ticket #427)
La valutazione rilevanza salva relevance_criteria come mappa piatta
{criterio: optionValue} (AssetController::score), ma showScoringModal
leggeva prev[key].value (forma a oggetto) -> sempre undefined -> nessuna
combo pre-selezionata alla riapertura. Letto ora il valore-stringa diretto,
con fallback retro-compatibile alla forma a oggetto {value:...}.

Fix solo frontend (JS inline in assets.html), nessuna migrazione.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 18:36:33 +02:00
DevEnv nis2-agileandClaude Opus 4.8 bd1e95542c [FIX] ARIA chat: indicatore 'sta scrivendo' + input multi-riga (Shift+Invio)
Risolve #418/#420 (ARIA sembrava non rispondere / serviva riaprire il pannello):
l'attesa della risposta /api/ai/ask (RAG+Claude, alcuni secondi) non mostrava
alcun indicatore -> ora compare 'ARIA sta scrivendo...' con input disabilitato
(anti doppio-invio). Indicatore rispetta prefers-reduced-motion (AGID/WCAG).

Risolve #425: il campo di scrittura era <input> a riga singola -> ora <textarea>
con Invio=invia, Shift+Invio=a capo, auto-crescita e aria-label.

common.js e' statico (no PHP/USR2): bump cache-buster ?v=20260623 su 39 pagine
(public + admin) + version.json 1.24.6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 18:13:38 +02:00
DevEnv nis2-agileandClaude Opus 4.8 05a1436d8c [I18N] Modulo Fornitori bilingue IT/EN (#8b) — v1.24.5
- supplier-portal.html (portale esterno OTP): i18n self-contained (dizionario inline + toggle IT/EN, ~79 stringhe), flusso OTP/token/ID/API invariato; domande questionario restano dati (non tradotte).
- supply-chain.html (interno): i18n estesa (5->25 data-i18n + 97 I18n.t).
- i18n.js: +82 chiavi sp.* (38->120). Cache-buster i18n.js su supply-chain.html. node --check OK, pagine 200, parita IT/EN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:32:18 +02:00
DevEnv nis2-agileandClaude Opus 4.8 14c4c2a7d2 [STD] Version-provenance sulle segnalazioni (feedback-version-provenance) — mig.062, v1.24.4
feedback_reports += client_surface/client_version/client_build; FeedbackController/Service li salvano; feedback.js li invia (surface web-v2 + version/build da version.json). Standard hub_standards id=30. Smoke verde (provenance salvata, test pulito). NB: il widget feedback.js è oggi dormiente (rimosso dall'iniezione 14/6) → plumbing pronto per la riattivazione.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 09:14:13 +02:00
DevEnv nis2-agileandClaude Opus 4.8 7b8a604739 [DOCS+KB] Gestione documentale: help (Permessi documentali) + i18n matrice IT/EN + ingest KB AI (v1.24.3)
- help.js: sezione 'Permessi documentali (ciclo di vita SGSI)' nel help Impostazioni
- settings.html: matrice permessi bilingue IT/EN (azioni/ruoli/hint) + cache-buster help.js
- KB AI: docs/kb/GESTIONE_DOCUMENTALE_SGSI.md + application/cli/ingest_doc_management_kb.php (ingerito in nis2_kb scope SYSTEM, ricercabilita verificata)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:40:28 +02:00
DevEnv nis2-agileandClaude Opus 4.8 e245e76671 [FEAT] Permessi documentali profilabili per ruolo (Impostazioni > Permessi documentali) — v1.24.2
I diritti sul ciclo di vita dei documenti SGSI sono ora configurabili per ruolo (matrice Ruolo x Azione, org-scoped) invece che cablati.
- mig.061: tabella isms_doc_permissions (org x capability x role x allowed)
- IsmsModelController: requireDocCapability() sostituisce i requireOrgRole cablati nelle 7 transizioni (edit/submit/approve/publish/reject/archive/new_version); default = comportamento precedente; super_admin sempre ok; endpoint GET/PUT /api/isms/docPermissions (canEdit)
- settings.html: tab 'Permessi documentali' con matrice checkbox (lettura per i membri, salvataggio Amministratore/Direzione)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:25:07 +02:00
DevEnv nis2-agileandClaude Opus 4.8 d29195b2c1 [FEAT] ISMS documenti: download unico set (Word) + editor in-app contenuto bozze (v1.24.1)
- exportAllDocumentsWord: GET /api/isms/documentsWordAll -> .doc unico (copertina+indice+page-break) di tutto il set non archiviato
- getDocument: GET /api/isms/documents/{id} (full doc per editor)
- updateDocument: guard NOT_EDITABLE su documenti non-bozza/revisione + ruoli estesi a board_member
- UI isms.js: pulsante 'Scarica tutto (Word)', 'Modifica' (editor contenteditable + toolbar + toggle HTML) per bozze/revisione
- help.js: editor in-app + download unico

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:11:57 +02:00
DevEnv nis2-agileandClaude Opus 4.8 68694a0f38 [FEAT] Gestione documentale ISMS — ciclo di vita ISO 27001 cl.7.5 + export Word (v1.24.0)
Ogni documento del Modello SGSI ha un ciclo di vita completo Bozza->In revisione->Approvato->Pubblicato->Archiviato con tracciamento (revisore/approvatore/pubblicatore, date, entrata in vigore, prossimo riesame) e storico versioni (snapshot). Download Word .doc modificabile per documento.
- mig.060: stati published/archived + 10 colonne tracciamento + tabella isms_document_versions
- IsmsModelController: submit/approve/publish/reject/archive/newVersion/versions/exportWord + listDocuments arricchito
- index.php: 8 route documents/{id}/(submit|approve|publish|reject|archive|newVersion|versions|word)
- isms.js: UI badge stato + pulsanti transizione + download Word + storico (IT/EN inline); cache-buster
- help.js: sezione 'Ciclo di vita dei documenti (cl.7.5)'
- 31 documenti di Nuova Agile pubblicati v1.0 (build_nuova_agile_p6_publish)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 13:40:21 +02:00
DevEnv nis2-agileandClaude Opus 4.8 7ad5d685b6 [UI] V2 (Bootstrap Italia/AGID) completata: migrate architecture+workflow (sidebar V2) + integrazioniext+mktg-api-doc (CSS BI); rimossa setup-org morta; v1.23.10
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:34:19 +02:00
DevEnv nis2-agileandClaude Opus 4.8 726a2a80ef [DOCS] #384 p.6 — help contestuale Matrice RACI su Policy + link Misure/Requisiti; cache-buster help.js; v1.23.9
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:46:38 +02:00
DevEnv nis2-agileandClaude Opus 4.8 99bd293745 [FEAT] #384 p.6 — Matrice RACI sulle policy + R/A obbligatorie in approvazione
Sezione 'Matrice RACI' nel dettaglio policy: assegnazione ruoli organigramma
(org_roles) alle responsabilita R/A/C/I via infrastruttura RACI esistente
(raci_assignments, object_type='procedure'). In bozza libera; PolicyController::approve
ora richiede almeno un ruolo R e uno A (422 RACI_REQUIRED). Nessuna migrazione.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:37:51 +02:00
DevEnv nis2-agileandClaude Opus 4.8 cb238e8380 [FEAT] #384 p.2.2/3 — colonne multi-valore + link attivi bidirezionali Policy<->Misure e Requisiti
Misure e Requisiti: colonna Policy (default) mostra tutte le policy collegate al
requisito come codici Policy.NN cliccabili -> aprono la policy (policies.html?policy=ID).
Policy: colonna Requisito NIS2 mostra tutti i requisiti collegati (codifica MISURA+
REQUISITO, es. 'DE.CM-01 comma 1.') come link -> evidenziano il requisito
(misure-requisiti.html?requisito=ID). Backend: FrameworkController::catalog espone
policies[] per requisito e PolicyController::list espone requisiti[] per policy
(org-scoped, tabella requisito_policy mig.059, try/catch con degrado al dato di default).
Solo lettura/visualizzazione: nessuna modifica dati, nessuna migrazione.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:25:16 +02:00
DevEnv nis2-agileandClaude Opus 4.8 02f3e69227 [FEAT] #384 p.2.2/3 fondazione — modello relazionale REQUISITO<->POLICY (mig.059)
Migrazione 059 additiva/idempotente/reversibile:
- policies.proc_code VARCHAR(16) NULL: ripristina la chiave di join verso il
  catalogo canonico (cfg_nis2_procedure/cfg_nis2_requisiti), che andava persa
  quando le 42 procedure di default venivano copiate nell'elenco Policy (mig.048).
- tabella requisito_policy: M:N org-scoped requisito<->policy (un requisito puo'
  avere piu' policy e una policy piu' requisiti) — base per p.2.2 e p.3.
- backfill sicuro: proc_code valorizzato su 42 policy (match esatto titolo) +
  116 associazioni di default popolate (INSERT IGNORE, WHERE proc_code IS NULL).
- PolicyController::ensureFrameworkProcedures: i nuovi seed conservano proc_code
  e popolano requisito_policy (try/catch dedicato, non blocca il seed).

Nessun impatto utente in questo passo (nessuna superficie legge ancora i nuovi
campi). UI multi-valore + link bidirezionali (2.2/3) nei prossimi cicli. v1.23.6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:12:07 +02:00
DevEnv nis2-agileandClaude Opus 4.8 8a8045904b [FEAT] #384 p.5 — allegati file su Policy (sezione Allegati nel dettaglio) + allowlist server-side upload evidenze
- policies.html: sezione 'Allegati' nel dettaglio policy (Bootstrap Italia card),
  upload (entity_type=policy) via /api/audit/evidence/upload, lista con download/autore/data
- AuditController::uploadEvidence: allowlist estensioni server-side (anti stored-XSS same-origin)
- version.json -> 1.23.5

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:00:01 +02:00
DevEnv nis2-agileandClaude Opus 4.8 6f3ca4b4bd [FEAT] #384 p.2.1 — ricodifica display Proc.->Policy in Misure e Requisiti
I codici delle policy/procedure di default si presentano con prefisso
"Policy." (es. Proc.01 -> Policy.01) tramite helper policyCode().
Solo etichetta UI: proc_code canonico invariato come chiave di join
requisito<->policy (non rompe il futuro link bidirezionale, punto 3).
v1.23.4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 18:45:05 +02:00
DevEnv nis2-agileandClaude Opus 4.8 6496c9655d [FEAT] #384 p.1 — terminologia Policy unificata
Colonna 'Procedura (default)' -> 'Policy (default)' in misure-requisiti.html;
colonna/dettaglio/campo modale 'Art./Articolo NIS2' -> 'Requisito NIS2' in policies.html;
testi descrittivi pagina Misure e Requisiti + guida help.js allineati al lessico 'policy'.
Cache-buster help.js?v= 20260631 -> 20260618p1 su 28 pagine. version.json 1.23.2 -> 1.23.3.
Punti 2/3/5/6 del ticket restano in lavorazione a fasi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 18:18:25 +02:00
DevEnv nis2-agileandClaude Opus 4.8 d739f0e678 [FIX] #384 p.4 — allineamento label/campi modale Policy (override floating-label BI) + cache-buster style.css 20260618
La modale 'Modifica Policy' (e ogni modale con form legacy .form-label) mostrava
l'etichetta sovrapposta al valore: BI definisce .form-group label{position:absolute}
(spec. 0,1,1) che vinceva sulla .form-label legacy. Aggiunto override
.form-group .form-label{position:static} (spec. 0,2,0). Bumpato ?v= di style.css
su 38 pagine. version.json -> 1.23.2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 18:00:55 +02:00
DevEnv nis2-agileandClaude Opus 4.8 2b6c5087ad [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>
2026-06-18 07:57:25 +02:00
DevEnv nis2-agileandClaude Opus 4.8 4fdeae7cee [FEAT] Modulo Controlli periodici (control monitoring §9.1/A.8.16) — v1.23.0
Registro controlli ricorrenti (owner/frequenza/metodo/controllo SoA-NIS2 collegato)
+ registro esecuzioni come EVIDENZA (data/esito/note) con avanzamento automatico
della scadenza; da esito non conforme generazione NC o NC+azione correttiva
(riuso non_conformities/capa_actions, nuova source 'monitoring'); report
scheda+storico stampabile; scadenze nel Calendario unico.

mig.057: periodic_controls + periodic_control_executions (FK CASCADE, UNIQUE
(organization_id, code)). PeriodicControlController (slug periodic-controls) +
controlli-periodici.html + CalendarController.srcPeriodicControls.

Fix da flotta di verifica avversariale (9 finding confermati):
- advance(): correzione overflow strtotime '+1 month' (31/01 -> 28/02, no salto
  mese) via DateTimeImmutable con clamp all'ultimo giorno valido (mensile/
  trimestrale/semestrale/annuale); giornaliero/settimanale/custom invariati.
- create(): retry-on-duplicate del codice CTL-NNN con UNIQUE (org_id, code).
- update(): array_key_exists per azzerare owner_role_id / next_due_date.
- delete(): rimossa cleanup morta su review_schedule (il calendario legge diretto).
- CalendarController.srcReviewSchedule: esclude internal_audit/stakeholder_activity
  (gia' con sorgente diretta) -> fix doppia comparsa nel calendario.
- calendario.html: TYPE_META 'periodic_control' (chip filtro + label + colore).

Additivo. Cache-buster ?v= bump (sidebar) + sw cache nis2-shell-v1.23.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 09:21:03 +02:00
DevEnv nis2-agileandClaude Opus 4.8 79ca72fba9 [FEAT] ISO-readiness: Audit interni (§9.2) + Riesame Direzione (§9.3) + Calendario scadenze (mig.055-056, v1.22.0)
Rilascio unico (3 moduli) costruito in flotta parallela + verifica avversariale.

MODULO A — Audit interni (ISO 27001 §9.2, mig.055): internal_audits + internal_audit_items;
codice AUD-NNN; checklist pre-popolata (clausole 4-10 + Annex A applicabili dal SoA);
esiti per riga; apertura non conformità collegata a NCR/CAPA (source polimorfico);
report HTML stampabile. InternalAuditController + internal-audits.html.

MODULO B — Riesame di Direzione (ISO 27001 §9.3, mig.056): management_reviews +
management_review_decisions; codice RD-AAAA-NN; INPUT aggregati automaticamente dai
moduli (gather: NC/CAPA, audit interni, rischi+trattamenti, KPI/score, obiettivi, formazione,
stakeholder, normative, scadenze), congelati nello snapshot all'approvazione; decisioni;
verbale stampabile. ManagementReviewController + management-review.html.

MODULO C — Calendario unico scadenze: aggregatore SOLA LETTURA (nessuna migrazione) di tutte
le scadenze (incidenti/policy/rischi/NC-CAPA/formazione/stakeholder/audit/riesame/review_schedule),
griglia mensile + lista + filtri + deep-link. CalendarController + calendario.html.

Integrazione: router (3 controller+actionMap), api.js, sidebar V2+legacy, help.js (3 sezioni),
i18n (IT+EN), review_schedule ENUM += internal_audit. v1.22.0 + sw cache + cache-buster 20260630.

Verifica flotta (28 agenti, 4 dim + avversariale): 9 finding confermati, TUTTI corretti —
MAJOR gatherRisks (risk_treatments.organization_id inesistente → JOIN risks); nextCode numerico
(no dup >99/anno); footer report audit con etichetta "ISO buona prassi, non obbligo"; help 24→25
clausole; ARIA tab/calendario; focus modali; tasti su celle calendario; rimossi helper api morti.

Smoke prod OK (calendario 18 eventi, audit AUD-001 25 item + report + audit→NCR + audit→calendario,
riesame gather/decisione/approve/report, gatherRisks ora available); org 151 ripulita. Additivo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 07:48:49 +02:00
DevEnv nis2-agileandClaude Opus 4.8 69873e21be [FEAT] Epic C / C5 — chiusura open-items: email gated + fonti + provenienza 92/87
- #7 (go-live ready): send() aggancia l'invio email del questionario/firma-lettura via
  EmailService, GATED dal kill-switch EMAIL_SENDING_ENABLED (oggi=false → ZERO invii;
  pronto al go-live). Magic-link assoluto via APP_URL; invio solo ai target con email
  valida; i link restano sempre disponibili per la condivisione manuale. Smoke prod:
  email_sent=0, email_enabled=false (nessun invio).
- #2 (prevale Simon): nis2_sources.php — aggiunta NOTA DI PROVENIENZA sul 92 vs 87
  (prodotto usa 92 = file referente; ACN ufficiale 87; divergenza nota/accettata).
  Numero NON modificato per decisione confermata.

Note operative (host, fuori repo):
- #8: EMAIL_MS_URL spostato sul path interno http://172.21.0.1:8081/api/emails
  (verificato 400 con X-Internal-Key = funzionante; pre edge-strip). Rollback: URL pubblico.
- #9: pre-equip PHP-CA del DB CONFERMATO (CA presente, app connessa in TLS 1.3); enforce
  ALTER USER nis2_user REQUIRE SSL resta azione DB coordinata con VIGILE (utente @%).

v1.21.3. Additivo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:32:27 +02:00
DevEnv nis2-agileandClaude Opus 4.8 3051983003 [FIX] Epic C / C5 open-items — upload ripristinato + fonti ACN aggiornate + UI
- UPLOAD ALLEGATI RIPRISTINATO in prod (causa: il sito è servito da php-fpm HOST e
  public/uploads era git:git senza ACL per www-data → move_uploaded_file falliva;
  fix host: setfacl -R -m u:www-data:rwx -m d:u:www-data:rwx public/uploads). Collaudato:
  upload interno+portale .pdf 201, .html→422. Risolve anche evidence_files/visure.
- nis2_sources.php (fonti-certe): Determina 333017/2025 datata 22 settembre 2025 e marcata
  STORICA/superata; aggiunta entry VIGENTE 379887/2025 (efficace dal 31/12/2025); URL ACN
  autorevoli (date-giorno marcate [da confermare sul testo ufficiale], verifica nis2-expert).
- UI attività: create+assign non lascia più attività orfane su errore (no duplicati su retry,
  messaggio chiaro) + assegnazione individuale richiede almeno un destinatario; blocco PWA di
  stakeholder-activities allineato (favicon-16/status-bar/application-name). v1.21.2.

ESCALATION (decisione di Simon, non modificata): nis2_sources.php riga 79 riporta "37 misure /
92 requisiti" per gli importanti (dato file Simon), ma la pagina UFFICIALE ACN indica "37/87".
Da riconciliare con Simon (open item #2). NON cambiato per rispettare la decisione bloccata.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:11:40 +02:00
DevEnv nis2-agileandClaude Opus 4.8 b917d2da14 [FIX] Epic C / C5 hardening — esiti flotta di verifica multi-agente (mig.054)
Corretti i finding confermati dalla verifica multi-agente (5 major + minori):

SICUREZZA
- Stored XSS allegati: da blocklist a ALLOWLIST di estensioni innocue (no html/svg/js
  renderizzabili same-origin) in StakeholderPortalController::attachment e
  StakeholderActivityController::storeUpload; nome file randomizzato (random_bytes).
- Magic-link: scadenza (mig.054 stk_activity_targets.token_expires_at; send() imposta
  scadenza attività+30gg o +90gg; resolveTarget() → 410 TOKEN_EXPIRED se scaduto);
  rate-limit per-IP sugli endpoint del portale; comment/attachment bloccati su attività chiusa.

CORRETTEZZA
- send(): NON rigenera token né azzera lo stato dei destinatari già responded/acknowledged
  (prima ne perdeva l'esito); imposta token_expires_at.
- assign individuale: semantica "replace" (rimuove i deselezionati non ancora conclusi) +
  guard su lista vuota (evita 'IN ()').
- update(): conserva assign_mode esistente quando si modifica solo stak_code.

UI/UX/A11Y
- Editor opzioni per domande a scelta singola/multipla (prima degradavano a testo nel portale).
- Etichette stato/tipo localizzate; risposte mostrate inline (no alert()); escAttr nel portale
  (escape virgolette negli attributi); ARIA su modali/tab; voce sidebar anche in common.js.

OPEN ITEM (NON regressione C5, pre-esistente e ambientale): l'upload allegati restituisce
UPLOAD_ERROR in prod — move_uploaded_file/is_uploaded_file fallisce nella topologia
proxy→fastcgi (stesso pattern di evidence_files/AuditController, mai funzionato: la dir
uploads/evidence non esiste). rename/copy come www-data funzionano. Da investigare lato infra.
Il resto di C5 (questionari, firma-lettura, commenti, calendario, portale) è pienamente operativo.

Smoke prod OK: allowlist (.html→422, struttura ok), opzioni scelta nel portale, send no-reset,
replace individuale, token_expires_at presente. Additivo. v1.21.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 22:52:31 +02:00
DevEnv nis2-agileandClaude Opus 4.8 4f386faae5 [FEAT] Epic C / C5.2b — Portale esterno stakeholder + sotto-dashboard feedback (mig.053)
Completa C5 (Epic C). Gli stakeholder rispondono in self-service tramite magic-link
(token SHA-256 per destinatario, NESSUN account/JWT); il compliance manager vede gli
esiti, i commenti e gli allegati nel dettaglio dell'attività.

- StakeholderPortalController (non-JWT, token-only): access / respond (questionario) /
  acknowledge (firma di avvenuta lettura) / comment / attachment. Submit one-shot (409),
  validazione risposte obbligatorie, anti-IDOR (un token = un solo destinatario),
  auto-completamento attività quando tutti hanno risposto/firmato.
- StakeholderActivityController: feedback (risposte per destinatario), comments
  (GET/POST), attachments (upload interno + lista; riuso evidence_files entity_type=
  'stk_activity', file sotto public/uploads/stk_activity/{org}/).
- mig.053: stk_activity_responses (answers JSON / acknowledged_at), stk_activity_comments
  (interni/esterni). Estende il seeder idempotente.
- Frontend: stk-portal.html (pagina pubblica dependency-free: questionario per tipo di
  domanda o testo+firma, commento, upload); dettaglio attività in stakeholder-activities.html
  con esiti, thread commenti e allegati.

Email disattivate (kill-switch) → i magic-link si condividono manualmente. Smoke prod OK
(access no-auth, respond+required+409, acknowledge+WRONG_TYPE, comment esterno/interno,
bad-token 404, feedback interno, auto-complete; cleanup org 151 pulita). Additivo. v1.21.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 22:20:14 +02:00
DevEnv nis2-agileandClaude Opus 4.8 a5ff29e0da [FEAT] Epic C / C5.2a — Attività stakeholder: questionari tipo + attività + calendario (mig.052)
Fondazione delle attività verso gli stakeholder (Simon C5 §4):
- Questionari tipo (template): tipo questionario-da-compilare / firma-di-avvenuta-lettura,
  domande (JSON) + collegamento m2m a procedure (policies) E a misure/requisiti del
  framework (cfg_nis2_misure/requisiti).
- Attività: basate (opz.) su un template, assegnabili a un CODICE stakeholder (tutti
  quelli di quel tipo) o a singoli stakeholder; pianificabili con data e scadenza.
- Calendario NIS2: review_schedule esteso (ENUM entity_type += 'stakeholder_activity');
  su create/update con scadenza la riga di calendario viene sincronizzata.
- Invio: genera un magic-link per destinatario (token SHA-256) per il portale esterno
  (C5.2b). Email disattivate (kill-switch) -> i link si condividono manualmente.

mig.052: stk_questionnaire_templates, stk_template_(procedures|misure|requisiti),
stk_activities, stk_activity_targets, stk_activity_procedures + ALTER review_schedule.
StakeholderActivityController + /api/stakeholder-activities/*; stakeholder-activities.html
(2 tab: attività / questionari tipo + dettaglio invio); voce sidebar V2; help/i18n.
Smoke prod OK (template m2m, attività by_code+calendario, assign, send magic-link,
anti-IDOR individual, negativi 422; cleanup org 151 pulita). Additivo. v1.20.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 22:12:11 +02:00
DevEnv nis2-agileandClaude Opus 4.8 c782aa54fa [FEAT] Epic C / C5.1 — Modulo Stakeholder: registro + matrice di Mendelow (mig.051)
Registro dedicato degli stakeholder (tabella `stakeholders`, NON una colonna su
suppliers: gli interni non sono fornitori; gli esterni si COLLEGANO opzionalmente
a un supplier esistente senza duplicare il dato).

- mig.051: cfg_stakeholder_types (30 di sistema Stak.01-30 + org-added da Stak.31),
  cfg_stakeholder_quadrants (4, range corretti: Q4 potere 0-2), stakeholders,
  stakeholder_procedures (m2m -> policies). Seeder idempotente seed_stakeholders.php.
- StakeholderController: list/create/update/delete + types/addType + quadrants + pickers.
  Quadrante CALCOLATO a read-time dai range config. Anti-IDOR su org_role/supplier/
  policy/tipo; coerenza kind (interni->organigramma, esterni->fornitore); punteggi 0-5.
- api.js: metodi stk*. stakeholders.html riscritta: registro + matrice di Mendelow
  in SVG dependency-free + modali (stakeholder, nuovo tipo). Voce sidebar V2 (common-bi.js).
- Help aggiornato; cache-buster ?v=20260626 su tutte le pagine; version 1.19.0; sw cache v1.19.0.

Ancoraggio GV.SC-02 (obbligo, art. 24 D.Lgs. 138/2024). La matrice di Mendelow e'
etichettata come BUONA PRASSI, non obbligo NIS2 (regola fonti-certe). Additivo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:54:57 +02:00
DevEnv nis2-agileandClaude Opus 4.8 30f4614b37 [FEAT] Epic C / C4 — inventario a 2 voci + sottoclassi configurabili (mig.050)
Simon C4: nell'inventario DUE sole voci principali; le sottoclassi le crea l'utente.
Riconduzione concordata (no perdita di dettaglio): hardware/network/facility -> ID.AM-01;
software/service/data -> ID.AM-02; personnel -> Organigramma (0 asset personnel presenti).

- Config: cfg_inventory_voci (2: ID.AM-01 Hardware, ID.AM-02 Software/Servizi/Sistemi),
  inventory_subclassi (12 default di sistema org NULL + sottoclassi org-scoped).
- assets.voce_code + subclass_id (FK). 20 asset esistenti MIGRATI (5 ID.AM-01 / 15 ID.AM-02).
- AssetController: list con label voce/sottoclasse; create/update validati (voce in whitelist,
  sottoclasse appartenente a org/default+voce); GET/POST /assets/subclasses; import (bulkUpsert)
  deriva la voce dal tipo legacy. asset_type resta come campo legacy (viste secondarie).
- assets.html: filtro per voce, form con selettore Voce + Sottoclasse (+ '+ aggiungi'),
  lista/dettaglio mostrano 'voce · sottoclasse', stat-chips per voce.
- FIX bug pre-esistente: apostrofo non escapato ('l'inventario') che ROMPEVA tutto il JS
  della pagina Inventario. + cache-buster common-bi.js (era a v=20260618, non bumpato dal fix C3).
Help + KB + i18n. Verifiche prod: subclasses 200, create voce/legacy OK, validazione 422,
JS valido (node --check), migrazione idempotente. version 1.18.4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 19:59:35 +02:00