- 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>
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>
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>
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>
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>
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>
Simon C3: l'analisi gap ACN non è un artefatto normativo e va eliminata; la conformità
emerge da C1 (elenco Misure/Requisiti + valutazione per requisito in org_requisito_state).
Ricognizione → migrazione → backup → rimozione (sicuro, 0 perdite):
- Tabelle acn_assessments/acn_assessment_responses VUOTE (0 righe) -> droppate (mig.049,
guard anti-drop se >0 righe). Ricreabili da 036. Nessun dato utente perso.
- Integrazione lg231: /api/services/gap-analysis legge 'assessments' generale, NON il modulo
ACN -> intatta. Verificato.
- RaciController ripuntato su cfg_nis2_misure (fonte canonica) al posto di acn_measures.json
(rimosso); validazione measure_code verificata (/raci/objects?type=measure -> 43 misure).
Rimossi: AcnAssessmentController, public/acn-gap.html, application/data/acn_measures.json,
routing acn-gap, voci sidebar (common.js + common-bi.js), metodi api.js acn*, sezione help 'acn',
chiavi i18n acn.*, mapping demo-selectors. Tour demo step 3 RIPUNTATO su misure-requisiti
(no step rotto, 10 step). Sidebar BI ora mostra 'Misure e Requisiti' (gap di common-bi.js sanato).
Verifiche prod: /acn-gap.html 404, /api/acn-gap/* 404, /misure-requisiti 200, RaciController OK.
Cache-buster ?v=20260624, version 1.18.3, SW v1.18.3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Flotta di verifica (14 agenti): sicurezza pulita, fedeltà del dato perfetta (codifica
Simon intatta, 0 scostamenti). Corretti i 6 finding confermati:
- MAJOR: PolicyController::ensureFrameworkProcedures ora in transazione + SELECT...FOR UPDATE
sulla riga org -> no procedure duplicate su fallimento parziale (rollback) o richieste concorrenti.
- MINOR: importer idempotente committato application/cli/seed_framework.php (DDL + upsert da
nis2_framework_seed.json) -> re-seed riproducibile/versionato (verificato 2 run = 0 duplicati).
- MINOR: allineato '92 requisiti' importanti (dato autoritativo Simon) in help.js + nis2_sources.php
(era 87, contraddizione interna); coerente '24 requisiti aggiuntivi essenziali' (116-92).
- MINOR (UI): troncamento anteprima valutazione sul RAW prima di esc() (no entita HTML spezzate).
- MINOR (UI a11y/AGID): modale valutazione con focus iniziale, ripristino focus, focus-trap.
Cache-buster ?v=20260623, version 1.18.2, SW nis2-shell-v1.18.2.
NB residuo da verificare: data esatta Determinazione 333017/2025 (mese senza giorno) in nis2_sources.php.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- help.js 'misure-requisiti': sezione 'Valutazione e stato di conformità' (stato per
requisito, valutazione del rischio che parte vuota, copia procedure di default) + nota.
- KB AI: doc SYSTEM su valutazione conformità + procedure/rischi di default (uuid 92979c05).
- Cache-buster ?v=20260622 (35 pagine), version.json 1.18.1, SW cache nis2-shell-v1.18.1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PolicyController::list ora, al primo accesso al modulo Procedure di un'org reale,
copia le 42 procedure di default (cfg_nis2_procedure) in policies come contenuto
editabile/cancellabile (decisione utente: copia per-org al 1° accesso).
- organizations.framework_procedures_seeded_at = idempotenza (no ri-copia).
- categoria policies assegnata da mappa famiglia NIST->ENUM; nis2_article=misura;
content riporta misure coperte + nota 'personalizzabile e cancellabile'.
- Salta org demo/sandbox (isDemo) per non inquinare il dataset dimostrativo.
- Best-effort: errore nel seed NON blocca la lista (log + return).
Smoke prod: org 151 3->45 policy (42 default, 10 categorie), idempotente al 2° accesso,
golden 151 poi ripristinato (cleanup). Additivo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
org_requisito_state (org x requisito -> stato + valutazione del rischio + note),
la valutazione 'parte vuota'. È da qui che emerge lo stato di conformità (sostituirà il Gap ACN, C3).
- FrameworkController: catalog ora include lo stato per-org di ogni requisito (risoluzione org
OPZIONALE con verifica accesso = anti-IDOR); nuovo POST /framework/state (upsert, requireOrgRole
org_admin/compliance_manager, validazione stato + requisito esistente).
- misure-requisiti.html: colonna Stato/Valutazione (se org) + modale di valutazione (stato,
valutazione del rischio, note). api.frameworkSetState.
Smoke prod: catalog has_org/org_class OK; upsert non_conforme persistito e riletto; ISOLAMENTO
multi-tenant verificato (org 152 non visibile a org 151). Additivo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fondazione data-driven (direttiva utente: configurabile via DB, no JSON hardcoded).
Fonte autoritativa: docs/simon/C1_Misure_Requisiti.xlsx + Vista (codifica Simon NON variata).
Tabelle system-level, sola-lettura per l'utente, configurabili da super_admin:
- cfg_nis2_ambiti (18 categorie NIST GV/ID/PR/DE/RS/RC)
- cfg_nis2_misure (43 misure; 6 solo-essenziali; area politica a..p, applies_important/essential)
- cfg_nis2_requisiti (116 requisiti; 92 importanti / 116 essenziali; ord N immutabile; FK misura/proc/risk)
- cfg_nis2_procedure (42 procedure di default Proc.01-42)
- cfg_nis2_rischi (84 rischi di default Risk.01-85)
Seed via application/data/nis2_framework_seed.json (import-only; runtime legge le cfg_*).
Applicato su prod (additivo, reversibile via DROP). Runner temp self-contained gia rimosso.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- public/js/help.js: rimossa unica occorrenza <i class="fas fa-comment-alt"></i>
(residuo non renderizzato dopo lo swap a Bootstrap Italia V2) + collassato lo
spazio ridondante, nella guida "Come aprire una segnalazione".
- Cache-buster mirato: help.js?v=20260613 -> 20260614 su 35 pagine public/*.html
(sibling common-bi.js/i18n.js/common.js/api.js/style.css lasciati invariati).
- public/version.json: 1.14.0 -> 1.14.1.
- Nessuna modifica a dati o schema DB. Ticket #367 (collaudo release supervisore).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Aprendo il pannello ARIA ora si nascondono nx-bar (campana/supporto) + ai-chat-fab -> niente overlap;
la X e Esc chiudono e ripristinano i pallini. Prima la X era fragile e il pannello copriva i bottoni.
Bump common.js?v=20260613d. (TRPG usa ai-assistant.js con stesso pattern toggle/hide.)
NIS2 aveva DUE sistemi feedback: feedback.js (pill cyan 'Segnala/Feedback' + modale scuro) + bug-reporter
AgileHub (campana+supporto). TRPG/ALLTAX usano solo bug-reporter (verificato: trpg ha app/js/bug-reporter.js,
nessun feedback.js). Rimosso l'auto-inject di feedback.js da common.js -> resta il trittico pallini
(notifiche + supporto + ARIA) come la suite. Verificato headless: feedback-fab assente, nx-bell/nx-bug/ai-chat
presenti. Bump common.js?v=20260613c (auto-refetch). feedback.js/api restano nel repo, non iniettati.
Risolve il 401 del bug-reporter ('Header X-API-Key o Authorization Bearer richiesto'): il widget ora
manda X-API-Key -> gateway ticket AgileHub OK (tenant 7, provenance corretta). Chiave fornita da AgileHub
(full tenant-key, va nel JS pubblico = stesso compromesso TRPG/ALLTAX; debito 'token low-priv' rimandato).
Bump versione common.js -> auto-refetch senza hard-refresh.
Risolve 'le modifiche non si vedono': /js/help.js era senza ?v -> browser serviva la versione cached
vecchia. 281 ref su 57 pagine + injection demo/bug-reporter aggiornate.