Promosse le pagine -bi a nomi ufficiali (contenuto BI), link -bi->plain, titoli '(BI)' rimossi.
La vecchia UI delle pagine app e sostituita. Backup: git (commit 36967f9) + /tmp/nis2-v1-backup.
Promosse (27): auth (login/register/onboarding/forgot/reset) + app (dashboard/assessment/
risks/incidents/policies/supply-chain/training/assets/reports/settings/isms/acn-gap/normative/
whistleblowing/kb/cross-analysis/service-continuity/guida/companies) + admin (index/orgs/users).
common-bi.js: sidebar BI ora linka le pagine ufficiali.
ESCLUSA: index.html (landing marketing 1298 righe) -> conversione dedicata a parte (la -bi
era 557 righe, rischio perdita contenuti). Pagine marketing/demo (presentation/simulate/
workflow/architecture/index-en) restano vecchio stile (fuori scope app autenticata).
Verifica produzione: 27/27 servite 200 con BI, 0 link -bi residui, 0 '(BI)' nei titoli,
auth intatta (401 su pwd errata), common-bi.js node --check OK.
Rollback: git revert HEAD (oppure ripristino da /tmp/nis2-v1-backup).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completa la copertura V2 al 100% delle pagine app autenticate (pre-swap).
Additivo: nessun file live HTML promosso ancora (lo swap e separato).
- 9 pagine app -bi: isms, acn-gap, normative, whistleblowing, kb, cross-analysis,
service-continuity (doc standalone), guida, companies
- admin/{index,organizations,users}-bi.html (path corretti, common-bi.js)
- help.js: +89 righe help contestuale pagine V2 (vincolo fonti certe)
- docs/kb/V2_UI_NAVIGAZIONE.md: doc UI/navigazione V2 per la KB AI (ARIA)
- Verifica: tutte servite 200, 0 CDN/FontAwesome, sprite validi, parita ID
Prodotto via Workflow a trittici (run wf_c4962fbe-93f) + fix manuale companies/sprite.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pipeline a trittici (Esecutore->Revisore->Controllore avversariale), 6 trittici T1-T6.
Tutto ADDITIVO: nessuna pagina/JS live modificata, produzione intatta. Swap non incluso.
- public/vendor/bootstrap-italia/ : BI v2.18.1 self-hostato (CSS+JS bundle+16 woff2+sprite), MAI CDN
- 16 pagine *-bi.html (login/register/onboarding/forgot/reset/index + dashboard/assessment/
risks/incidents/policies/supply-chain/training/assets/reports/settings)
- public/js/common-bi.js : sidebar/topbar in markup BI (additivo, riusa stessi ID + common.js)
- Verifica sul path servito reale: 16/16 -> 200, 0 CDN, 0 FontAwesome residui, parita ID/hook completa, WCAG AA
- docs/SISTEMA_TRITTICI_AGENTI.md : design del sistema a trittici
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Riscrittura completa: istruzioni concrete (login, registrazione 3 step, onboarding
4 step con campi reali, navigazione), per ogni modulo "cosa trovi e come si usa"
estratto dall'help contestuale del prodotto (help.js), FAQ, glossario. Rimosse le
interruzioni di pagina che lasciavano pagine vuote. Generatore self-contained
(estrae i contenuti da public/js/help.js via node).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lezione 2026-06-10 (3 outage): forzare SSL su una connessione via socket (DB_HOST
'localhost'/vuoto/path) da' PDOException [2002] "Cannot connect to MySQL using SSL"
su ogni richiesta -> app giu'. sslOptions() ora ritorna [] (resta in chiaro + log) se
DB_HOST indica un socket, anche col flag/env attivo. SSL si applica solo alle
connessioni TCP. Il socket e' IPC locale: nessun transito di rete da cifrare.
Cosi' riaccendere .db_ssl_on mentre l'app e' su socket NON puo' piu' causare outage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CONTEXT/runbook aggiornati con le conclusioni 2026-06-10 sera:
- L'app HTTP (php-fpm) usa il MySQL HOST via UNIX SOCKET (localhost), NON il container
(la "scoperta container" precedente era basata sul path CLI = falso positivo).
- TLS su socket non si applica e rompe (outage rientrato: rm flag + USR2).
- Switch socket→TCP via vault-net (172.30.0.1): CLI(root) OK ma fpm(www-data) esce con
source=gateway → masquerade → 135.x → 1045. Switch NON eseguito (sarebbe outage).
- Lezione: validare SEMPRE sul path fpm reale (diag servito da php-fpm), mai CLI/docker exec.
- Pre-equip codice resta committato e inerte (flag OFF); attivabile quando il path TCP
fpm sarà grantabile (bind 172.21 in finestra con restart MySQL).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
database.php: Database::sslOptions() — TLS verso MySQL gated (env DB_SSL=true o
flag-file application/config/.db_ssl_on), DEFAULT OFF. PDO::MYSQL_ATTR_SSL_CA +
VERIFY_SERVER_CERT=false; fail-safe se CA assente (resta in chiaro + log: un
VERIFY=false senza CA connetterebbe in chiaro silenziosamente). Merge con '+'
(preserva chiavi-intere PDO). Default-OFF provato inerte (base + [] === base).
Validato da 2 agenti: runtime app coperto 100% da Database::getInstance (0 raw PDO
in application/); review adversariale = SICURO default-OFF, nessun bug.
.gitignore: flag-file + db-ca.pem. Runbook: docs/TLS_DB_PREEQUIP_NIS2.md.
NB topologia verificata: l'app usa il DB CONTAINER (db->172.21.0.4, 8.0.45), non il
MySQL host -> CA + enforce vanno sul container db (a cura VIGILE).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La regola .form-group input {width:100%} si applicava ANCHE al checkbox del
consenso, gonfiandolo a tutta la riga e spingendo il testo a capo parola per
parola. Aggiunto override .consent-label input[type=checkbox] {width:16px}.
Ora testo in linea su 2 righe, colore normale, link Privacy policy + asterisco cyan.
Verificato con render headless Chrome.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Due bug nel form 'Richiedi accesso':
1. La regola generica .form-group label span {color:red} (pensata per l'asterisco
obbligatorio) coloravadi rosso TUTTO il testo del consenso.
2. Lo <span> dentro il label flex non aveva larghezza -> andava a capo parola per
parola (incolonnato verticalmente).
Fix: classe dedicata .consent-label con span flex:1 + min-width:0 + colore normale;
l'asterisco resta cyan. Testo ora in linea accanto al checkbox.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Slide 9 (ex 'Modello commerciale') -> 'Edizioni': tolto 'abbonamento annuale /
nessun costo per utente', i 3 tier descrivono solo perimetro funzionale per
profilo (PMI/Enterprise/Consulente). Contatti: 'Pricing: abbonamento annuale'
-> 'Attivazione: su richiesta, offerta personalizzata'. Niente accenni a prezzi
o gratuita.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"Nessuna carta di credito richiesta" suggeriva erroneamente che il servizio
sia gratuito. Sostituito con messaggio neutro su accesso controllato + rapidita
operativa, senza accennare a costi o modalita di pagamento.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Linguaggio tecnico ma chiaro, allineato al prodotto reale (v1.13.0):
- Hero: subtitle con doppia gap analysis (Art.21 + misure di base ACN) + AI consulente;
stats -> 116 requisiti ACN mappati, 12 moduli, Art.23, RAG su fonti certe.
- Moduli (8 -> 12): NUOVA card "Gap Analysis ACN" in evidenza (37/87 importanti,
43/116 essenziali, Framework Nazionale GV/ID/PR/DE/RS/RC); Gap Analysis Art.21
separata; Supply Chain con Portale Fornitori (OTP/magic-link, campagne ricorrenti);
AI Consulente RAG (203 requisiti, no allucinazioni normative); Knowledge Base
multi-livello (vendor/studio/org con isolamento).
- Step "come funziona", meta description e trust-items allineati.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#1 CRITICO aiAnalyze: askWithRag ritorna ['answer','sources','rag_used'], non
una stringa. Ora estrae 'answer' (ai_summary) e salva 'sources' in
ai_recommendations. Prima salvava il JSON intero in ai_summary.
#2 ALTO corpus RAG: acn_requirements.json aveva 188/203 testi TRONCATI alla
prima riga PDF (es. GV.PO-01#1: 84 char invece di 838). Rigenerato dai testi
INTEGRALI di acn_measures.json (87+116, zero troncamenti). Ri-ingest Qdrant.
#3 MEDIO catalog(): org non classificata dava entity_level=null + warning PHP
$totals[null] + TypeError frontend. Ora 422 ENTITY_LEVEL_REQUIRED come create().
#4 MEDIO guida cap-5 GV.RR-04: "figure chiave dell'organigramma" era errato e
auto-contraddittorio -> "personale autorizzato + amministratori di sistema,
valutazione esperienza/capacita/affidabilita" (allineato testo ACN).
#5 BASSI: openAcn try/catch (no unhandled rejection su Riprendi); badge
importante/essenziale IT/EN; overall_score=null (non 0.0) se tutti N/A.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verifica punto-per-punto delle affermazioni del revisore (GV.RR-04, GV.PO-01,
codifica importanti/essenziali) contro i testi ufficiali ACN. Esito: finding
fondato; imprecisioni di contorno documentate. Razionale del modulo Gap Analysis ACN.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- guida.html: nuovo cap-5b "Gap Analysis ACN" (importanti vs essenziali con
tabella 37/87 e 43/116, 6 funzioni FW, scoring, piano d'azione, quale-uso-quando).
- help.js: voce contestuale 'acn' + mappature page->help->guida (cap-5b).
- i18n.js: nav.acn_gap + 15 chiavi acn.* IT/EN.
- version.json -> 1.13.0.
- AI/KB: gia a posto — nis2_sources.php cita Allegati 1/2 (37/87, 43/116) con
GV.RR-04/GV.PO-01; 203 requisiti ACN gia in Qdrant per il grounding.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- public/acn-gap.html: catalogo requisiti consultabile, wizard per funzione FW
con accordion misure/requisiti, opzioni attuato/parziale/non attuato/N.A.,
autosave batch debounce, risultati con punteggio per funzione + piano d'azione
gap + analisi AI. Badge livello soggetto (importante/essenziale). IT/EN inline.
- api.js: metodi acn* (catalog/list/create/requirements/respond/complete/report/
aiAnalyze) che spacchettano l'envelope e lanciano errore su success=false.
- common.js: voce sidebar "Gap Analysis ACN".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Assessment di SECONDO LIVELLO sulla Determinazione ACN 164179/2025 (non le 10
lettere generiche Art.21, gia coperte). Distingue soggetti importanti/essenziali:
- IMPORTANTI (All.1): 37 misure, 87 requisiti
- ESSENZIALI (All.2): 43 misure, 116 requisiti
- application/data/acn_measures.json: dataset canonico estratto dai testi
UFFICIALI ACN (Allegati 1+2), testi requisiti INTEGRALI (no troncamenti),
flag per-requisito importante/essenziale. Validato 37/87 + 43/116, zero
discrepanze vs codici di riferimento.
- AcnAssessmentController: catalog/list/create/get/requirements/respond/complete/
report/aiAnalyze. Pre-popola requisiti applicabili per entity_level, scoring
per funzione FW (GOVERN/IDENTIFY/PROTECT/DETECT/RESPOND/RECOVER), grounding AI
sui 203 requisiti ACN gia in KB. Anti-IDOR, snapshot testo immutabile.
- Migrazione 036: acn_assessments + acn_assessment_responses (APPLICATA su host).
- Router: acn-gap controllerMap + actionMap.
Origine: finding revisore (la Gap Analysis Art.21 non e l'autovalutazione ACN).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
APP_ENV sul container e' 'production' ma l'ambiente contiene SOLO dati demo:
nessuna mail deve partire. Cambiato il default di EMAIL_SENDING_ENABLED da
"true in production" a "false sempre". Le email partono SOLO con override
esplicito EMAIL_SENDING_ENABLED=true. Fail-safe contro invii accidentali.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ambiente con soli dati demo: NESSUNA email deve partire. Aggiunto guard
all'inizio di sendViaRelay() e sendViaTemplate() (gli UNICI due punti che fanno
HTTP al relay -> copre tutti i canali: incidenti, training, inviti, reminder,
welcome, password reset, feedback, OTP portale fornitori).
EMAIL_SENDING_ENABLED in config: default false in sviluppo, true in produzione,
override via .env. Quando false l'invio viene loggato e scartato (return false),
nessuna chiamata di rete.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Il metodo sendViaTemplate() non era stato salvato (Edit silenziosamente fallito):
requestOtp() del portale chiamava un metodo inesistente -> errore inghiottito dal
try/catch -> OTP MAI inviato. Ora presente: POST /api/emails/send con template +
data (campo canonico relay AgileHub) + alias vars, senza logEmail (OTP fuori da email_log).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
I metodi corretti sono upsertBatch() e deleteByFilter(), non upsertPoints/deletePoints.
Ingest eseguito su host: 203 requisiti ACN confermati in Qdrant nis2_kb.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scripts/ingest-acn-requirements.php: indicizza in Qdrant nis2_kb un chunk per ogni
requisito ACN (87 importanti Allegato 1 + 116 essenziali Allegato 2 = 203), scope
SYSTEM, entity_type=requisito_acn, con payload citabile (code/subcategory/function/
req_index/entity/allegato/requirement_text). Point id UUIDv5 deterministico ->
idempotente. Usa upsertPoints/deletePoints/ensureCollection reali di VectorService.
ESEGUITO su host (docs/ non e' bind-mountato nel container): 203 punti confermati
in Qdrant. L'AI askWithRag ora fa grounding sul singolo requisito GV.SC e lo cita.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- createCampaign(supplierId): invia un template a un fornitore via questionnaire_campaigns
(token sq_ retrocompat, scadenza/ricorrenza/reminder_offsets configurabili, crea
supplier_user per accesso OTP, email invito col link al portale). Migrazioni 034+035
applicate su host.
- campaigns(): cruscotto con semaforo scadenze (success/warning<=7gg/danger scaduto)
+ answers_count per campagna.
- computeNextReminder(): helper per il prossimo reminder dagli offset.
- Route: GET:campaigns, POST:{id}/campaigns. api.js: getQuestionnaireCampaigns,
createQuestionnaireCampaign.
Smoke: rotte 401 (router+auth ok). php -l + node --check OK. USR2 applicato.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nel commit 3e5b75b gli edit a normative.html e companies.html erano falliti
(file-not-read / string-not-found): il pulsante '?' non partiva. Ora corretti.
Verificato: tutte e 4 le pagine (whistleblowing/normative/companies/kb) hanno
help.js + HelpSystem.init=1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completata la rifinitura help: i 4 moduli che avevano contenuto (commit c1d3328)
ma non il pulsante '?' ora chiamano HelpSystem.init().
- whistleblowing/normative: aggiunto HelpSystem.init() (help.js gia' incluso).
- companies: aggiunti include i18n.js + help.js + HelpSystem.init().
- kb: aggiunto include help.js + HelpSystem.init().
Ora ogni modulo della piattaforma ha aiuto contestuale. version 1.11.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contenuto help per i 4 moduli che ne erano privi (sezioni + references a fonti reali:
D.Lgs.24/2023 per whistleblowing, Art.20-21 per normative, RAG per kb, multi-tenant
per companies). Aggiunte voci in _pageMap e _guideAnchor. Verificato eseguendo help.js
con node: getAvailablePages() = 16 (12 + 4 nuovi), tutti e 4 presenti, sintassi valida.
NOTA: il cablaggio di help.js nelle 4 pagine HTML (script include + HelpSystem.init)
e' ancora da fare per rendere visibile il pulsante '?' su quelle pagine.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La chiave di produzione nis2_mktg_... (scope admin:licenses) era in chiaro in
mktg-api-doc.html e integrazioniext.html, servite pubblicamente -> chiunque poteva
ottenere una chiave di gestione licenze B2B. Sostituita con placeholder
nis2_mktg_xxxxx in entrambe le pagine (6 occorrenze totali).
NOTA: la chiave reale va ANCORA RUOTATA nel DB (api_keys) in coordinamento con
mktg-agile, poiche' resta nella history git e potenzialmente nota. Questo commit
elimina solo l'esposizione web continua.
Trovato da audit sicurezza multi-agente (F1, priorita' top).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug CRITICO da test multi-agente: kb.js::getJwt() leggeva localStorage 'access_token'
ma l'app salva il JWT sotto 'nis2_access_token' -> ogni chiamata KB inviava
Authorization: Bearer (vuoto) -> 401 -> pagina KB completamente inutilizzabile
(upload/list/search/delete). Stesso pattern del bug this.delete.
Inoltre kb.html non aveva il blocco init (checkAuth/loadSidebar/I18n.init) presente
in tutte le altre pagine -> sidebar vuota e nessun redirect a login.
Fix: kb.js usa nis2_access_token; kb.html aggiunge i18n.js + init auth/chrome.
node --check OK. version 1.10.6.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- suggestRisks: usa employeeRange() invece di employee_count esatto nel prompt
(coerenza anonimizzazione con gli altri metodi verso Anthropic).
- crossOrgAnalysis: era l'unico metodo con affermazioni normative senza il blocco
fonti certe nel system prompt -> ora lo inietta (regole 1-5, no invenzioni,
orientamento non vincolante).
- EmbedService: commenti "1024 dim" -> 512 (il codice forza output_dimension=512,
coerente con la collection nis2_kb size=512).
- VectorService::ensureCollection default 1024 -> 512: rischio latente di creare
una collection incompatibile se chiamato senza argomenti.
php -l OK su tutti e 3. version 1.10.5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CRITICO #2 — register() generava il token SENZA jti, ma requireAuth lo rifiuta
(JWT_NO_JTI): l'utente appena registrato veniva sbattuto fuori al primo
getMe/completeOnboarding e doveva rifare login. Ora register crea una riga
active_sessions con jti e genera access+refresh token col jti, come login().
CRITICO #1 — DELETE /auth/sessions/<jti> (revoca sessione singola) tornava 404:
il jti è esadecimale (non numerico), il router cadeva nel ramo "nome composto"
e generava solo {action}/{camelResource}, mai {action}/{id}. Aggiunto fallback
{action}/{id} con id passato come STRINGA (revokeSession(string $id) lo accetta).
Il candidato composito resta primo, quindi evidence/upload ecc. non si rompono.
php -l OK su entrambi. version 1.10.4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- dashboard: complianceScore ora ritorna 'score' (overall_score ultimo assessment);
la gauge usa avg_implementation se >0, altrimenti il punteggio assessment.
Prima mostrava 0% per org con gap analysis ma senza modulo controlli (H2).
- risks.html backToList(): ripristina la vista corrente tra le 4 (table/matrix/fair/kri),
prima cadeva sempre su table/matrix (H1); renderDetail nasconde tutte e 4.
- risks.html loadFair(): legge risksRes.data.items (endpoint paginato), prima
risksRes.data.risks era undefined e il dropdown FAIR restava vuoto (M1).
php -l + node --check OK. version 1.10.3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug live introdotto in v1.10.0: le viste Categorie e Template usavano
querySelector('#app-modal .modal-body') ma showModal (common.js) crea l'overlay
con id='modal-overlay'. Il selettore restituiva null -> le modali non si
popolavano (lista template/categorie vuota dopo il loading). Corretto a
'#modal-overlay .modal-body' (5 occorrenze). Trovato durante il test multi-agente.
Inline JS validato. version 1.10.2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- public/js/i18n.js: +38 chiavi sp.* (IT+EN) per pulsanti header, categorie, template, import.
- supply-chain.html: data-i18n sui 4 pulsanti header; l'import CSV ora mostra il
dettaglio delle righe scartate (d.errors[] dal backend) in un <details> e tiene
aperta la modale se ci sono errori (prima si chiudeva sempre, errori invisibili).
- version 1.10.1.
Inline JS validato (node --check). File statici -> live via nginx.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Colma il gap UI confermato dalla review (backend Fase 1 c'era, UI no):
- Form fornitore: dropdown "Categoria fornitore" alimentato da getSupplierCategories
(ensureCategories con cache); category_id ora salvato (backend gia' fixato 9fbf72a).
- Pulsante "Categorie": modale con lista preset+custom, CRUD sulle custom
(preset non modificabili), gestione errore CATEGORY_IN_USE.
- Pulsante "Template": modale lista template + vista dettaglio domande read-only
con badge nis2_ref/tipo/peso/obbligatoria — mostra le 26 domande GV.SC del DB.
- Target modale via querySelector('#app-modal .modal-body') (showModal usa class).
Inline JS validato (node --check exit 0). File statici -> live via nginx. version 1.10.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug trovato da review: il client HTTP base definisce del(), non delete().
deleteAsset chiamava this.delete() -> TypeError, eliminazione asset falliva.
Era l'ultimo this.delete() residuo (i metodi nuovi Fase 1 gia' usano del()).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Allineato il commento del regime obblighi: Allegato 4 essenziali / Allegato 3 importanti
(coerente con AIService 0d748c6 e la fonte certa Allegato3/4.txt). La logica era gia'
corretta, solo il commento era fuorviante.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Il commit 6079311 aveva corretto solo help.js: gli edit ad AIService erano falliti
(old_string non corrispondente) e l'AI continuava a citare l'allegato SBAGLIATO in
produzione. Questo li applica davvero:
- classifyIncident: Allegato 4=essenziali, Allegato 3=importanti (era invertito).
Verificato su docs/nis2/allegati_acn/Allegato{3,4}.txt. La logica IS-4 era gia'
corretta (blocca IS-4 per importanti); era sbagliata solo l'ETICHETTA dell'allegato.
- decorrenza relazione finale: "1 mese DALLA NOTIFICA delle 72h (non dalla data
dell'incidente)", allineato ad Art.23 e guida.
- authoritativeSourcesBlock: +regola 4 (orientamento NON vincolante, art.22 GDPR) e
+regola 5 (ENISA/NIST/ISO best practice non vincolanti). Iniettato in TUTTI i prompt.
- IncidentController:62 commento allineato (Allegato 4 essenziali / 3 importanti).
php -l OK su entrambi.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review coerenza multi-agente tra AI/help/guida (forte adesione normativa):
CONTRADDIZIONE B (la piu' rischiosa) — inversione Allegato 3/4 incidenti.
Verificato sulla fonte certa (docs/nis2/allegati_acn/Allegato3.txt e 4.txt):
Allegato 3 = soggetti IMPORTANTI, Allegato 4 = soggetti ESSENZIALI.
AIService::classifyIncident e help.js dicevano l'inverso -> CORRETTI.
(La guida era gia' corretta.) Cambiava quali obblighi si applicano a chi.
CONTRADDIZIONE A — decorrenza relazione finale.
AIService diceva generico "30 giorni"; ora esplicito "entro 1 mese DALLA NOTIFICA
delle 72h (non dalla data dell'incidente)", allineato all'Art.23 e alla guida.
DISCLAIMER non-parere-legale (gap: la guida ce l'ha, l'AI no).
authoritativeSourcesText: +regola 4 (orientamento NON vincolante, valutazioni da
confermare con compliance/legale, art.22 GDPR) e +regola 5 (ENISA/NIST/ISO = best
practice non vincolanti; obblighi da Dir.2022/2555 + D.Lgs.138/2024 + Determine ACN).
Iniettato in tutti i prompt che usano il blocco fonti certe.
php -l OK, help.js node --check OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug trovato da review UI multi-agente: create() e update() non includevano
category_id nell'INSERT/UPDATE, quindi la categoria assegnata a un fornitore
(dropdown UI / API) veniva silenziosamente persa. La colonna esiste da mig 033.
Ora create lo salva (?: null) e update lo include nei campi whitelisted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gli edit a guida.html nel commit b53d2af erano falliti (file non riletto) e b53d2af
conteneva solo version.json: questo applica DAVVERO le modifiche.
- Intro: box Avvertenza (non parere legale, valutazioni caso per caso).
- cap-9: paragrafi Categorie/template configurabili + Import CSV/CMDB/API + nota
interpretativa perimetro fornitori (GV.SC come famiglia Framework, Det.ACN 164179 All.2).
HTML bilanciato (15 section, 199 div).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Findings review normativa multi-agente sulla guida (forte adesione + interpretazioni chiare):
- Intro: box "Avvertenza" — la guida non costituisce parere legale, le valutazioni di
ambito/classificazione/significativita' vanno confermate caso per caso su fonti ufficiali e
con consulente. (Finding #2: disclaimer generale prima assente.)
- cap-9: nuovi paragrafi "Categorie fornitore e questionari configurabili" e "Import massivo
(CSV/CMDB/API)" che descrivono le funzioni Fase 1 prima non documentate (Finding #8).
- Nota interpretativa sul perimetro fornitori: la selezione dei fornitori "rilevanti" e' una
valutazione caso per caso, non lista chiusa; GV.SC citato come famiglia del Framework
Nazionale (Det. ACN 164179/2025 All.2), non come nome di feature.
HTML bilanciato (15 capitoli, 16 section open/close). version.json -> 1.9.2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- bulkUpsertSuppliers: il ramo category_id esplicito (import API/CSV) ora verifica
che la categoria sia un preset (org 0) o della stessa org, come gia' fa il ramo
category_slug. Evita di scrivere suppliers.category_id di un'altra org (dato sporco
cross-org). Finding review multi-agente (MINORE, correttezza dati).
- docs/sql/032,033: header "PROPOSTA DI DESIGN (NON applicata)" -> "APPLICATA su
produzione 2026-05-31" (sono effettivamente applicate). Evita confusione operativa.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review normativa multi-agente sul template NIS2 base (18/26 citazioni gia' perfette):
- Q24/Q26 (GV.SC-05): la citazione attribuiva al "§1" un testo che e' il TITOLO della
misura. Corretto col testo verbatim del §1 reale (inserimento requisiti GV.SC-01.1.b
in contratti/bandi).
- Q22 (GV.SC-02): idem, distinto titolo da §1; il "punto di contatto sicurezza" e'
dichiarato come buona pratica, non obbligo testuale.
- Q4 (RTO), Q7 (ISO 27001), Q22, Q26: aggiunti disclaimer [Interpretazione del prodotto]
dove la piattaforma fa una scelta metodologica (soglie RTO, ISO come proxy, mappature)
non imposta dalla lettera della norma.
0 citazioni inventate, 0 articoli errati. Forte adesione normativa: dove c'e'
interpretazione ora e' dichiarata esplicitamente. JSON valido (26 domande).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug HIGH in produzione: le stringhe JS single-quoted a riga 1201/1207 contenevano
l'apostrofo non escaped (l'incidente, e') -> SyntaxError parse-time nel blocco
<script> inline -> loadIncidents() non partiva, tabella bloccata sullo spinner,
"Nuovo Incidente" inerte. La pagina Incidenti era di fatto inutilizzabile.
Fix: escape \' nelle due stringhe del decision-tree significativita' Art.23.
Inline JS validato con node --check. File statico -> live via nginx senza USR2.
Trovato da review multi-agente.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le due sezioni help non erano entrate nel commit precedente (edit string-not-found).
Ora presenti: 'Importazione fornitori (CSV/API)' e 'Categorie e questionari
configurabili' con nota interpretativa esplicita (perimetro fornitori = scelta
documentata, non parere legale).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sezioni help contestuali 'Importazione fornitori (CSV/API)' e 'Categorie e
questionari configurabili' con nota interpretativa sul perimetro fornitori critici
(scelta documentata dell'organizzazione, non obbligo automatico). Aggiunto
riferimento Determinazione ACN 164179/2025 Allegato 2 (GV.SC-01/02/04/05/07).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tre Edit dei commit f85876f/73f47df erano falliti silenziosamente (string-not-found)
e committati come fatti: questo li applica davvero.
- public/js/api.js: 14 metodi client Fase 1 (categorie/template/domande/import).
Usano this.del() (il metodo base e' del(), non delete()).
- public/supply-chain.html: pulsante "Importa" in header + openImportModal()/parseCsv()/
runSupplierImport() reali (prima era stub). Modale CSV con upsert per external_ref.
- public/js/help.js: sezioni "Importazione fornitori (CSV/API)" e "Categorie e questionari
configurabili" + riferimento ACN Allegato 2 GV.SC. Nota interpretativa esplicita sul
perimetro fornitori critici (scelta documentata dell'organizzazione).
Seed template NIS2 base (26 domande) ora applicato DAVVERO su org 129 via host DB
(il commit 8d7a50a era fallito: il CLI docker punta a nis2-db, il web/host usa host MySQL;
risolto errore collation utf8mb4 con COLLATE esplicito). Idempotenza verificata.
Inline JS validato (node --check). api.js/help.js validati.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Help contestuale supply-chain: aggiunte sezioni "Importazione fornitori (CSV/API)" e
"Categorie e questionari configurabili" (template NIS2 base GV.SC, Allegato 2 ACN).
version.json -> 1.9.0.
Nota: capitolo guida.html dedicato + chiavi i18n EN + KB AI restano da completare
in un passaggio dedicato (il modulo questionari ha ancora UI parziale: backend+import
pronti, editor no-code template e portale OTP nelle fasi successive).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fase 0 modulo questionari fornitori + fix bug produzione.
mail() built-in e' VIETATA dallo standard email-relay v1.0 e non recapitava nel
container. EmailService::send() ora instrada tutte le email via
POST /api/emails/send-raw del relay centralizzato email-automation-ms, header
X-Internal-Key, env multi-source (workaround clear_env PHP-FPM Alpine, pattern
SsoHelper::postInternal). Email mascherate nei log (GDPR, maskEmail()).
Beneficiano tutti i 6 caller esistenti senza modifiche: sendQuestionnaire
(supply-chain), forgotPassword (auth), notifiche incidenti, formazione,
feedback, contact.
Smoke test E2E produzione: send() => TRUE, email_log status=SENT (product=nis2).
Hot-reload USR2 su nis2-app. version.json -> 1.8.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decisioni utente: auth OTP/magic-link (no password, tabella supplier_users separata),
categorie predefinite+personalizzabili, scadenze configurabili, portale su supplier-portal.html
(stesso dominio). Schema DB, API (/api/supplier-portal/*), flusso OTP, 4 fasi, sicurezza
(isolamento totale da users interni, no IDOR), domande aperte per approvazione. NO codice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completa il recepimento dei findings della review (3 esperti). Tutte le 5 osservazioni tester + critici/warning/migliorie applicati.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Il backend sendQuestionnaire/questionnaire-status esisteva ma la UI non lo esponeva (guida prometteva una funzione non azionabile). Aggiunti:
- bottone 'Invia questionario' in lista (azioni) e in dettaglio fornitore
- funzione sendSupplierQuestionnaire (chiede email opzionale, mostra il link sq_ generato)
- api.js: sendSupplierQuestionnaire + getSupplierQuestionnaireStatus
Distinto 'Valuta (interna)' da 'Invia questionario' (link esterno). E2E prod: 201 + link generato.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🔴 cap-7: i criteri 500utenti/4h/100k€ NON sono legge -> marcati come indicatori di triage del prodotto; criterio legale reale = art.23 c.3 (grave perturbazione/perdite o impatto considerevole su terzi); soglie quantitative -> Allegati 3/4 ACN + Reg.(UE)2024/2690. Plain-box: 30 giorni -> un mese, terminologia preallarme/notifica/relazione finale.
🔴 cap-2: aggiunto terzo criterio dimensionale (totale bilancio: grande >43M, media >=10M); data center spostati tra importanti; nota DORA lex specialis banche/finanza; servizi fiduciari qualificati/non qualificati.
Fonti: Dir.(UE)2022/2555 art.23/art.34, AmbitiNIS2, Determina 164179/2025.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- reports.html: RIPRISTINATO da HEAD (working tree corrotto: 89 tab 'Report Esecutivo' duplicati, ~146 righe spazzatura non committate; HEAD era sano). La corruzione era servita live, ora risolta.
- Nuovo tab 'Requisiti ACN': mostra gli 87/116 requisiti specifiche-base per funzione (GV/ID/PR/DE/RS/RC) con % compliance, summary stati e cambio stato inline (select -> PUT).
- api.js: getAcnRequirements + updateAcnRequirement.
JS validato (node --check). Endpoint E2E già verde (org importante 87 req, PUT implemented ok).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Allinea il PRODOTTO alla guida/normativa portando la compliance dal livello 10 misure Art.21
al livello operativo dei requisiti ACN (Framework Nazionale 2025).
- Migrazione 031: acn_requirements (catalogo) + org_acn_requirement_status (stato per-org)
- Seed da Allegati 1-2 ACN (fonte certa, parsing verificato): 87 importanti + 116 essenziali = 203 requisiti reali
- AuditController: acnRequirements (GET, per entity_type org: importanti 87 / essenziali 116, summary per funzione GV/ID/PR/DE/RS/RC, % compliance) + updateAcnRequirement (PUT stato+evidenza)
- Route audit/acnRequirements GET/PUT
- guida.html: fix refuso cap-5 (residuo 'otto categorie...no' -> '10 categorie x 8, quattro modalita')
E2E prod: org importante -> 87 req; PUT implemented -> compliance aggiornata.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Scaricati da acn.gov.it: Allegato1 (soggetti importanti), Allegato2 (essenziali),
Allegato3/4 (incidenti significativi). PDF + .txt (pdftotext -layout). Sono la fonte
certa per allineare il questionario Gap Analysis ai requisiti operativi reali del
Framework Nazionale 2025 (funzioni GV/ID/PR/DE/RS/RC). Importanti: 43 sottocategorie;
essenziali: 49. (Conteggio requisiti di dettaglio: parsing tabellare da rifinire.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completa l'allineamento iniziato in 5e2534e (FAIR/KRI/benchmark). Ora tutte e 10 le nuove
funzionalita sono documentate nei capitoli pertinenti. Ancore cap-X intatte, HTML bilanciato.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 sezioni aggiunte nei capitoli pertinenti (cap-3/4/6/7/8/9/11/12), stile coerente con
l'esistente (in parole semplici / esempio / cosa dice la norma). Ancore cap-X invariate
(help.js continua a linkare i capitoli giusti). HTML bilanciato.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Il commit precedente NON conteneva questo fix (Edit fallito su ancora errata). Ora applicato:
JOIN su MAX(completed_at) -> subquery correlata (ultimo completato, tie-break id, LIMIT 1),
una sola riga per org anche con timestamp identici. E2E: org con 2 assessment stesso TS -> peers=4 (non 5).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ingestIncident: insert in loop (max 5) -> rigenera incident_code su collisione UNIQUE
(sotto carico SIEM il random a 6 cifre poteva collidere -> 500 = alert perso). Inoltre la
race su external_ref (due alert simultanei) ora ritorna 200 dedup invece di 500.
- controlsMonitoring (services): UPDATE auto-stale avvolto in try/catch come la gemella in
AuditController (degrada con grazia se control_evidence_auto manca).
Verificato E2E: ingest 201, dedup 200.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Migrazione 030: UNIQUE uq_policy_version su policy_versions (de-dup prima, idempotente).
approve() ora usa INSERT ... ON DUPLICATE KEY UPDATE -> riapprovare la stessa versione
aggiorna lo snapshot invece di duplicarlo. Verificato E2E: 2x approve v1.0 -> 1 sola riga.
- diff(): sostituito il confronto set-based (falsi negativi su righe duplicate/riordino) con
un vero diff LCS line-by-line con posizioni. Verificato E2E: bump v1->v2 -> added 2, removed 1 corretti.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Due vulnerabilità trovate dalla review indipendente:
1. connectorOrgGuard usava users.role (GLOBALE) invece del ruolo per-org -> la feature
era ROTTA per gli utenti reali (org_admin reale ha users.role='employee' -> 403 sulla
propria org). Ora ancora l'autorizzazione al parametro di ROUTE {id} e legge
user_organizations.role. Verificato E2E: globale=employee + per-org=org_admin -> 200;
non-membro su altra org -> 403 (no IDOR via header X-Organization-Id).
2. secret-strip era una denylist case-sensitive/non-ricorsiva aggirabile (Client_Secret,
apiKey, connection_string, segreti annidati). Sostituita con ALLOWLIST ricorsiva
(sanitizeConnectorConfig): solo campi non sensibili noti, valori forzati a stringa+troncati.
Verificato E2E: input con 11 varianti di segreti -> DB contiene solo {account_id, region}.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bug PRE-ESISTENTE (commit 7823898 del 2026-02-20, presente anche in 94d7867): gli apostrofi in
"dell'attivita" (riga 512) e "l'anno" (riga 513) chiudevano le stringhe a singolo apice ->
SyntaxError che azzerava l'INTERO blocco <script> della pagina Rischi (tabella, matrice, FAIR,
KRI, dettaglio: tutti gli onclick davano ReferenceError). La pagina era servita 200 ma JS morto.
Trovato dalla review multi-agente (agente frontend). node --check ora OK.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verificato E2E in prod: list 200 (8 tipi), save m365 201, secret 'client_secret' STRIPPATO (assente da config DB), delete 200, openConnectors servito in companies.html.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Richiesta utente: 'le credenziali si configurano nella card per ogni azienda cliente'.
- Migrazione 029: tabella org_connectors (config NON segreta + vault_key_alias + secret_status). NESSUN segreto nel DB.
- OrganizationController: listConnectors/saveConnector/deleteConnector + connectorOrgGuard (org_admin/compliance_manager propria org, o firm che la gestisce, o super_admin)
- Difesa: i campi segreti (client_secret/api_key/...) inviati vengono STRIPPATI prima del salvataggio (verificato E2E: non finiscono nel DB)
- saveConnector ritorna cli_hint col comando vault-cli per caricare il segreto (write-path vault = solo CLI admin, confermato leggendo server.js: solo GET /v1/credentials/*)
- UI: pannello 'Connettori' nella card di companies.html (8 tipi, tenant/client id, toggle attivo, stato segreto, modal)
- Route organizations/{id}/connectors GET/PUT/DELETE (type nel body)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>