Competizione coi GRC enterprise sul risk management quantitativo:
- FairService: simulazione Monte Carlo FAIR (PERT su TEF e Loss Magnitude),
ALE in EUR con percentili P10/P50/P90 + istogramma, deterministico (seed da input)
- RiskController::computeFair -> POST /risks/{id}/fair (persiste parametri+ALE)
- RiskController::fairRegister -> GET /risks/fairRegister (portfolio ALE EUR)
- KRI: listKri/createKri/updateKri (GET/POST /risks/kri, PUT /risks/kri/{id})
con stato semaforo green/amber/red su soglie+direzione
- Migrazione 026: risks += parametri FAIR + ale_min/ml/max/mean; nuova tabella kri
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Aggiunto da AgileHub-side (VIGILE) su autorizzazione esplicita utente, dopo che la
sessione NIS2 ha scoperto sul campo 2 lezioni critiche:
1. opcache.validate_timestamps=Off -> ogni edit .php richiede kill -USR2 1 nel
container FPM (bind-mount non basta a servire il nuovo bytecode)
2. modifiche non committate vengono revertate dal cron ticket-agent (caso reale:
commit d5d83bb ha revertato index.php di una Feature 1 WIP)
- Nuovo doc docs/INCOMING_FROM_AGILEHUB_2026_05_30_release_workflow_hot_reload.md
- Sezione ATTIVITA PRIMARIA inserita in CLAUDE.md subito dopo lo standard timezone
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Il FAB ARIA (common.js) chiamava POST /api/ai/ask ma il controller non esisteva
(assistente AI rotto). Creato AiController::ask -> AIService::askWithRag con RAG su KB
+ grounding fonti certe. Verificato in produzione: rag_used=True, cita Ambiti NIS2 / Determina ACN.
Fix DNS Qdrant: nei worker php-fpm (musl) getenv e gethostbyname NON funzionano per
hostname Docker single-label; funziona solo un IP letterale. VectorService fallback ->
172.21.0.3 (fpm-safe); QDRANT_URL compose resta hostname per CLI. Vedi nota drift in VectorService.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
L'IP hardcoded Qdrant 172.21.0.5 era driftato a .3 (container senza IP statico) e
con php-fpm clear_env=no la env QDRANT_URL=172.21.0.5 (morta) veniva usata -> RAG web rotta.
Fix: QDRANT_URL e fallback VectorService usano l'hostname http://nis2-qdrant:6333,
risolto via Docker DNS sia in CLI sia in php-fpm. Verificato retrieval end-to-end (287 chunk).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completamento UI per gli endpoint backend già attivi (commit e4f9e91):
- admin/users.html: colonna Azioni con pulsante "Impersonate" per utenti non-super_admin
attivi → salva token originale in sessionStorage, sostituisce con quello impersonate,
redirige a dashboard
- js/common.js: banner persistente arancione "Modalità Impersonate" in tutte le
pagine quando sessionStorage ha impersonate origin → pulsante "Esci impersonate"
ripristina token originale e torna ad admin/users
- settings.html: nuovo tab "Preferenze" (lingua/tema/timezone/notifiche email+in-app)
con form salva via PUT /auth/preferences
- settings.html: nuovo tab "Branding" (solo super_admin / consulente) con
brand_name/logo_url/primary_color/secondary_color, PUT /branding
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- simulate.html: aggiunta card BIG (10 aziende, 18 fasi) con url
simulate-nis2-big.php, label BIG in SIM_LABELS, confirm dialog dedicato
- simulate-nis2-big.php: fix training assign user_id → user_ids (array)
come richiesto da TrainingController::assignCourse()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ServicesController: nuovo endpoint GET /api/services/full-snapshot
Aggrega gap-analysis, measures, incidents, training, deadlines,
compliance-summary in una sola chiamata (reduce 6 round-trip → 1)
Parametro ?days=N per finestra deadlines (default 30, max 365)
- public/index.php: route GET:fullSnapshot aggiunta all'action map services
- public/simulate-nis2-big.php: wrapper SSE per simulate-nis2-big.php
Esegue il simulatore come sottoprocesso CLI con NIS2_SSE=1 e
streama l'output al browser tramite Server-Sent Events
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /services/gap-analysis — gap per dominio NIS2 Art.21 con mapping MOG 231 pillars
- GET /services/measures — compliance_controls con mog_area e nis2_article derivati
- GET /services/incidents — incidenti con Art.23 CSIRT compliance per step (24h/72h/30d)
- GET /services/training — corsi + completamento board (Art.20 compliance flag)
- GET /services/deadlines — scadenze aggregate da 4 sorgenti con ?days= filter
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- simulate-nis2-b2b.php: 6 scenari autonomi (SIM-B1→B6):
mktg login, invito con recipient data, validazione pubblica,
registrazione ridotta, provision org, login con org, API Key M2M
- public/simulate-b2b.html: UI terminale dark con flow diagram e SSE streaming
- public/register.html:
- Registrazione ridotta: con invito che ha recipient data mostra banner
"Ciao [Nome]!" + campi pre-compilati read-only + solo password richiesta
- Post-register con inviteToken: chiama provision automaticamente,
salva nis2_org_id in localStorage, redirect a dashboard.html
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- public/simulate-nis2.php: riscritta con proc_open come lg231 test-runner.
Lancia simulate-nis2.php come subprocess CLI con NIS2_SSE=1, streama
ogni riga SSE al browser immediatamente senza buffering Apache/FPM.
Stderr del subprocess → eventi SSE 'error' visibili nel terminale.
- simulate-nis2.php: aggiunto supporto NIS2_SSE=1 (env var).
Quando NIS2_SSE=1, IS_CLI=false → output SSE anche da sottoprocesso.
API_BASE usa sempre server prod in modalità subprocess.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- autoResetDemo(): cancella tutti i file /tmp/nis2_ratelimit/*.json
all'avvio così la re-esecuzione immediata non incappa in "Troppi tentativi"
- ensureUser(): aggiunge retry login se register fallisce con "email già
registrata" (caso in cui dbSeedUser ha inserito l'utente ma il primo
login aveva avuto un errore transitorio)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SSE heartbeat (commento ': heartbeat') ogni 25s in simLog() per mantenere
viva la connessione attraverso proxy/CDN con timeout 300s (pattern lg231)
- Apache vhost: Timeout 1800 + ProxyTimeout 1800 (su Hetzner direttamente)
per simulazioni che richiedono 8-12 minuti
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
max_execution_time=30 in Apache php.ini interrompeva la simulazione dopo 30s.
La simulazione completa richiede 8-12 minuti.
Aggiunto: set_time_limit(0), ignore_user_abort(true), memory_limit=256M.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DocumentRoot è public/ → simulate-nis2.php (root progetto) era 404.
Aggiunto public/simulate-nis2.php: wrapper che imposta NIS2_SIM env
e include il simulatore reale tramite require __DIR__/../simulate-nis2.php.
Aggiornato URL in simulate.html: ../simulate-nis2.php → simulate-nis2.php.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- simulate-nis2.php: autoResetDemo() via PDO pulisce dati demo prima di ogni
run (SIM-01→05), skip per SIM-06 indipendente. Rimuove tutte le tabelle
org_id>4 eccetto audit_logs (trigger immutabile).
- simulate.html v2.0: rimosso pulsante "Reset Dati Demo" (chiamava endpoint
inesistente /api/admin/reset-demo). Aggiunti: confirm dialog con lista
aziende + durata, spinner sul bottone, nota auto-reset visibile, run history
localStorage (ultimi 5), card SIM-06 B2B License Provisioning, console
phase-banner stile lg231.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Il pattern */30 chiudeva prematuramente il docblock /** causando
parse error. Sostituito con spazio per chiarezza.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- help.js: nuova sezione 'feedback' con 6 sotto-sezioni (come usare FAB,
risposta AI, password gate, le mie segnalazioni, worker autonomo, consigli)
- i18n.js: 30 chiavi IT/EN per tutto il sistema feedback
- AIService::callAPI: system prompt esteso con lista completa moduli NIS2 Agile
- AIService::classifyFeedback: system prompt NIS2-aware
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>