Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
119 KiB
Contesto Ultima Sessione
Il 2026-05-29 ci sono state DUE sessioni: pomeriggio e mattina (TRPG). Il 2026-05-30 sessione lunga: gap competitivi P1/P2/P3 + connettori + review multi-agente + fix.
2026-08-25 — 2 segnalazioni Simon: Documenti Istituzionali (bug NO_ORG) + Organigramma freeze — RISOLTE
Memorie: project_institutional_docs, reference_createicons_mutationobserver_loop, project_prod_topology_host_fpm.
Segn. 1 — Documenti Istituzionali (#499 ri-segnalato, screenshot: solo "Aggiungi voce", niente 5 voci): era un BUG vero, non cache (mia diagnosi iniziale "cache" SBAGLIATA, corretta con Simon). Causa: InstitutionalDocsController::list() usava requireAuth()+getCurrentOrgId() ma currentOrgId e' popolato SOLO da requireOrgAccess() → endpoint sempre NO_ORG (400) → loadDocs riceveva standards=[] → card con sola "Aggiungi voce". Non ha MAI funzionato a runtime. Fix (commit 396783c): backend list() usa resolveOrgId() (risolve X-Organization-Id senza lanciare) + restituisce SEMPRE le 5 voci; frontend dashboard.js renderDocsCard()+STD_FALLBACK (5 voci sempre visibili anche se API fallisce). Buster dashboard.js 20260825, php-fpm ricaricato. Verificato puppeteer (996003): 5 voci visibili, api standards=5, 0 errori. GOTCHA: endpoint per-org non-membership → usare resolveOrgId(), NON requireAuth()+getCurrentOrgId().
Segn. 2 — Organigramma "Nuovo Ruolo" bloccava il sito: freeze ELIMINATO. Causa NON era ciclo dati (0 cicli in org_roles, verificato) ma l'anti-pattern MutationObserver(subtree)→lucide.createIcons() che si auto-ritriggerava (createIcons muta il DOM). Fix (commit 1f9d837): debounce+disconnect. + hardening guardia anti-ciclo client+server buildTree/orgNodeHtml (commit bfda75b, php-fpm reload). Bonus proattivo: stesso anti-pattern trovato e corretto in raci.html + competenze.html (commit 69f3894). Test puppeteer: "Nuovo Ruolo" apre il modale (visibile), 0 errori — il "nessun effetto" di Simon era cache/versione intermedia → hard-reload. Help online: aggiunta sezione «Documenti istituzionali» (commit 9eeef78).
Email a Simon + copia cristiano.benassati@ (201) per entrambe (con scuse per la diagnosi "cache" errata sul #499). ⚠️ Chiave SSH temporanea rigenerata da Cristiano (.ssh-temp/hetzner_key, finestra 12h) — scade ~22:00 UTC. Nessuna nuova migrazione (prossima=068).
2026-08-03 — Rework pagina Rischi (richieste Simon): matrice 4 fasce + toggle inerente/residuo, trasversali a 12 colonne — LIVE v1.27.0
Memorie: project_ticket_500_risks_page (aggiornata), project_modulo_azioni.
Nuove richieste Simon (via email, non ticket — #501 già RESOLVED) sulla finestra Rischi, tutte IMPLEMENTATE e LIVE (commit 4809d8e rework + f421f4e help/manuale/KB):
- Matrice 5×5 a 4 fasce (eliminata 10-14): 1-2 basso verde, 3-8 medio arancione, 9-12 alto rosso tenue, 15-25 critico rosso — stesse soglie dell'Alert.
matrixBand()+ CSS.matrix-cell.band-low/med/high/crit(rimosse.level-1..10). - Toggle Inerente/Residuo sopra la matrice (
setMatrixView('derived'|'trans', 'inherent'|'residual')+matrixPoints()legge likelihood/impact o residual_*). - Rischi trasversali: 3.1 sezione in FONDO (matrice requisiti
#view-matrix/gridrisk-matrix-grid-derivedrisalita dopo la tabella derivata); 3.2 tabella#risks-table-bodyrifatta a 12 colonne (renderRisksTable+scoreSelT/onTransversalScoreChange→api.updateRisk, update parziale P/I+residuo); 3.3 tastibtn-ai-suggest/btn-new-riskspostati in testa (tolti dall'header pagina); 3.4 2ª matrice#view-matrix-trans/gridrisk-matrix-grid-transversalcon toggle; 3.5 tasto Elimina (confirmDeleteRisk) per riga.viewRiskDetail/backToListaggiornati (nascondono/mostrano anche allhazard-section + view-matrix-trans).
Verificato puppeteer 996002: 4 bande, 2 toggle funzionanti, 14 th (12 col), 6 delete, 24 select inline, sezione in fondo, 0 errori. version 1.27.0 + SW cache bump. help.js (§matrice+trasversali) + manuale (§6 matrici, §7 trasversali) + KB RISCHI_MODELLO.md re-ingest (3 chunk). Buster help.js 20260801. Email Simon + copia cristiano.benassati@ (201). Nota aperta: Alert dei trasversali apre viewRiskDetail (scheda rischio/trattamenti), NON il popup crea-azione (che è sui rischi dai requisiti via requisito_actions) — proposto a Simon di unificarlo se lo vuole. Ancora solo frontend; DB invariato (prossima mig 068).
2026-08-01 — #501 punto 3 RISOLTO (all-hazard comprimibile) + ticket RESOLVED
Memorie: project_ticket_500_risks_page, project_modulo_azioni.
Compromesso su #501 p3 APPROVATO da Cristiano/Simon e IMPLEMENTATO (commit ed0b16e). La sezione "Rischi aggiuntivi (all-hazard)" NON cancellata ma trasformata in <details> comprimibile rinominato «Rischi trasversali (approccio multirischio / all-hazards) — art. 24 D.Lgs. 138/2024»: header sempre visibile + contatore #allhazard-count, apri-se-vuota (updateAllhazardHeader: count 0→aperta per audit, >0→chiusa=meno ingombro), riservata admin, matrice fuori dal details. i18n custom_heading/custom_help IT+EN + help.js + manuale + KB (re-ingest 3 chunk) allineati; buster help/i18n 20260731. Validato nis2-expert PRIMA. Collaudo puppeteer 996002: contatore 6, chiusa di default, matrice separata, 12 col ok, deploy statico verificato curl (0 residui "aggiuntivi"). Email+nota #501 (201). Ticket #501 → RESOLVED (PATCH /tickets/501/status {status:RESOLVED} + header X-Tenant-Id:7 → 200, verificato). Tutti e 3 i punti chiusi (scorrimento + filtro classe + all-hazard riorganizzata). Mail di fine lavoro a Simon e copia a cristiano.benassati@gmail.com. Gotcha: endpoint cambio stato ticket = PATCH /tickets/{id}/status.
2026-07-31 — Rischi 12 col: 2 correzioni Simon (LIVE) + analisi ticket aperti + proposta #501 p3 (no codice)
Memorie: project_modulo_azioni, project_ticket_500_risks_page.
A. Riscontro Simon sulla 12-col (applicato e LIVE, commit 8799620): (1) titoli di GRUPPO sui tripletti Prob/Impatto/Valutazione → "Rischio Inerente"/"Rischio Residuo" (risks.html thead + i18n grp_inherent/grp_residual IT+EN); (2) ritirata eccezione #3: soglie alert riportate a spec Simon 15-25/9-12/3-8/1-2 — con P,I∈1-5 i prodotti 7/11/13/14 non esistono → nessun buco (alertBand resta >=9; corretti help.js/manuale/KB). Cache-buster help/i18n 20260730b. Verificato via curl (statico live). KB RISCHI_MODELLO.md re-ingerita (3 chunk) — via application/_ingest-risks.php+_risks_model.md temp (scripts/ NON montata in nis2-app). Email+nota #501 (201).
B. Analisi ticket aperti (richiesta Cristiano): ticket-ms tenant 7 → 1 solo aperto = #501 (IN_PROGRESS), 21 chiusi. Punti 1-2 fatti; unico rimasto = punto 3 (cancellare sezione all-hazard).
C. Proposta #501 p3 — validata PRIMA di intervenire, NESSUN codice toccato: nis2-expert ha confermato di NON cancellare (obbligo all-hazards art.21.2 Direttiva→art.24 D.Lgs.138) e affinato: etichetta "Rischi trasversali (approccio multirischio / all-hazards) — art. 24 D.Lgs. 138/2024" (evitare "aggiuntivi/opzionali"), collassabile ma header+contatore sempre visibili e aperta se vuota (audit), citare Direttiva art.21 vs decreto art.24, allineare tutti i testi (risks.html:438-439, i18n:185-186, help.js:614). Riepilogo "come lo risolverei" inviato a Simon (email 201 + nota #501 201), segnalando che la parola finale è di Cristiano (tocca decisione #500). In attesa di OK prima di implementare.
Gotcha: ingest KB → application/ (scripts/ non montata in nis2-app); ticket-ms message richiede header X-Tenant-Id: 7; chiave SSH temporanea si azzera con pulizia notturna 03:27.
2026-07-30 — 🟢 Gestione Rischi 12 colonne (integrazione prototipo "Modulo Azioni" di Simon) LIVE v1.26.0
Memorie: project_modulo_azioni, project_ticket_500_risks_page, project_session_release_mgmt, project_cruscotto_studio.
Contesto in arrivo: Simon ha inviato un prototipo HTML ampio ("Modulo Azioni": ciclo miglioramento azioni↔audit↔rischi/policy/KPI↔RACI↔budget↔calendario) + spec tabella Rischi a 12 colonne + 2 bug/domande. Richiesta Cristiano: analizza, confronta con agenti normativi, integra con le eccezioni motivate, aggiorna help/KB, manuale, invia a Simon.
A. Analisi (3 agenti): Explore (mappatura codebase: KPI/Budget assenti=greenfield; capa_actions figlia-di-NC=attrito; risks ha già inerente+residuo ma non sulla vista requisiti; calendario read-only; "RISCHIO(DEFAULT)"=cfg_nis2_rischi.risk_descr) + nis2-expert + iso-27001-expert. Verdetto: impianto valido; 🔴 col.12 (residuo→conformità requisito)=confusione 2 piani; "preventiva" rimossa ISO 2013; banda 13-14 scoperta; "Danno €"→Impatto; soglie=metodo interno non obbligo.
B. FASE 1 IMPLEMENTATA E LIVE (v1.26.0): tabella public/risks.html a 12 colonne (inerente+residuo, editing INLINE P/I 1-5 auto-save, Alert a bande→popup+crea azione, codice→Misure e Requisiti). Backend: mig 067 (org_requisito_state += residual_likelihood/impact; tabella requisito_actions), RiskController::derivedList(+risk_descr+residuo)/setState(+residuo)/requisitoActions list+create. Verificato puppeteer (sandbox 996002). 7 eccezioni normative applicate (la #1: residuo NON auto-aggiorna conformità → flag Esito residuo + stato esplicito). Commit 8a0bc5c+ea57928+371e98c.
C. Contorno: help.js+i18n IT/EN+KB RISCHI_MODELLO.md (re-ingest via application/_ingest-risks.php DENTRO nis2-app — host non raggiunge Qdrant 172.21.0.5) + manuale docs/MANUALE_GESTIONE_RISCHI_12COL.md. Email a Simon (integrazione+7 eccezioni+manuale, 201) + nota ticket #501 (201). Release v1.26.0 (version.json+SW cache).
D. Altri due punti Simon (stessa sessione): parere normativo su all-hazard #501 p.3 (mantenere, mai rimuovere — obbligo art.21.2/art.24) inviato ticket+email; bug «Le mie aziende»→dashboard = cache stale (Simon 330 super_admin, companies.html carica OK live; navigazioni SW=network-first→reload risolve). ⚠️ design da confermare: admin studio con role=org_admin+cf_id NON vedono il Cruscotto (gate solo consultant/super_admin).
Gotcha sessione: prod=host php-fpm (systemctl reload php8.4-fpm, oltre USR2 nis2-app); ticket-ms message endpoint richiede header X-Tenant-Id: 7; git add -A fallisce su doc INCOMING di altro utente→add esplicito. Roadmap: Modulo Azioni completo (registro azioni/audit bidir/KPI/budget/calendario) = fasi 2-5, attesa conferma eccezioni da Simon. Prossima mig=068.
2026-06-27 — 🟢 UI V3 (revamp top-nav) LIVE + allineamento help/i18n/KB/ARIA + mail tester + test completo + polish
Memorie: project-ui-v3-rollout, project_aria_chat_wiring, project-bootstrap-italia-rollout (superato).
A. Revamp UI V3 (top-nav navy/oro, Inter+Lucide self-host) — LIVE
Sostituita la sidebar Bootstrap Italia (V2) con una top-nav a mega-menu. Flusso: manutenzione flag-gated (.htaccess 503 + maintenance.html, bypass ?mnt=) → FASE 0 foundation (public/css/v3.css, public/js/topnav-v3.js, public/vendor/inter|lucide, dashboard pilota) commit b0fc190 → flotta di agenti su 32 pagine app (incl. admin/) commit a4150b5 → verifica (200/no-CDN/no-residui-V2) → rimossa manutenzione. v3.css ha un layer widget compat (modal/toast/form/btn-variant/spinner) + utility (.btn/.text-muted/.sr-only) + alias var V2→V3 così common.js funziona senza style.css. Reskin (wiring pagina preservato, id/data- intatti). Escluse standalone: supplier-assessment/simulate*/service-continuity. ⚠️ Manutenzione durata ~5h (vs ~1h stimata) per riavvii del processo che uccidevano gli agenti di sfondo (i file scritti però persistono).
B. Allineamento help / traduzioni / KB / ARIA al top-nav — commit 58b4bdb
Per evitare riferimenti alla vecchia sidebar: topnav-v3.js = aggiunte pagine orfane in nav (Matrice RACI→Struttura interna, Scadenziario→Monitoraggio, Compliance Journey→Guida) + cablato data-i18n; i18n.js = nuove chiavi nav V3 (gruppi + voci) IT/EN; AIService::navigationMapBlock riscritta sul menu in alto V3 (sync con topnav-v3.js NAV_GROUPS, non più common-bi.js) + reload php-fpm host+container; help.js = corrette 3 "sidebar"; KB nis2_kb = docs/kb/V3_UI_NAVIGAZIONE.md ingerito host-side (scripts/ingest-v3-ui-doc.php, 3 chunk SYSTEM, rimossi i chunk V2). Cache-buster ?v=20260627n sui 3 JS. ⚠️ aria-guide "Mostrami dov'è" → fallback pulito (naviga alla pagina, niente spotlight su mega-menu): rework opzionale.
C. Mail tester
Annuncio V3 "di nuovo online + cosa provare (menu in alto, Connettori Discovery, ARIA)" a m.tagliavini@/s.fattori@/presidenza@/cristiano.benassati@gmail.com (4×201 via send-raw). Finestra manutenzione protratta → scuse incluse.
D. Test completo di rilascio (puppeteer) + 2 bug trovati e fixati
T1–T6 tutti verdi: 36 pagine 200/no-CDN/no-residui-V2; tutto il JS (file + inline di 56 pagine) senza errori di sintassi; i18n completo (no chiavi mancanti app); 14 endpoint API con dati reali coerenti col DB e nessun leak multi-tenant; ARIA navigazione allineata al top-nav; pagine pubbliche 200 + manutenzione OFF. Render autenticato REALE con puppeteer-core (drive /usr/bin/google-chrome, pipe:true per aggirare il blocco porta-debug dell'env; token QA = mint jti+riga active_sessions; chiave org localStorage = nis2_org_id): top-nav si disegna (9 voci, 29 mega-link, FAB, utente/org), dati reali caricano, screenshot ok. 🐞 Bug 1 (commit a223923): ARIA data-aware MORTA via chat reale — AiController::ask chiamava requireAuth() ma non requireOrgAccess() → getCurrentOrgId() null → org_data_ok sempre falso. Fix: risolve l'org da X-Organization-Id→param→org primaria, gate membership anti-spoof. 🐞 Bug 2 (commit 95e77a6): titoli i18n V3 mr.title/normative.title mancanti → I18n.t() ritorna la chiave → l'<h1> mostrava "mr.title". Aggiunte le 2 chiavi; cache-buster i18n ?v=20260627p.
E. Polish 3 note minori — commit a71d1a0
- aria-guide "Mostrami dov'è" ora funziona su V3:
data-navsu voci+mega-link intopnav-v3.js;pointTocerca in.bar-nave apre il mega-menu (.aria-guide-open) puntando la voce; coach "menu in alto". Verificato a video (mega "Rischi" aperto, spotlight su "Inventario"). [supera la nota in B] - segnala-anonimo.html CSP
font-src 'self'→'self' data:(fontdata:sbloccato). - Cache-buster
topnav-v3.js/common.js/aria-guide.js?v=20260627t. - Ticket NIS2 (ticket-ms, tenant 7): coda VUOTA — 21 totali tutti RESOLVED/CLOSED, ultimo 25/6, nulla post-mail-V3. Gotcha:
?product=NIS2ritorna 0 → listare senza filtro + headerx-tenant-id:7, filtrare client-side suproduct.
F. Release v1.25.0 + 🐞 bug "sessione scaduta = pagina bloccata" (segnalato da Massimo)
v1.25.0(commit64e10b1): bumpversion.json(MINOR, UI V3) + cache SWnis2-shell-v1.25.0con PRECACHE shell V3.- 🐞 Bug api.js (commit
41476c8) — Massimo "sono tornato su nis2 e non funziona": utente/aziende "--", inventario bloccato su Caricamento, niente FAB segnalazioni. NON è rottura V3: riprodotto con puppeteer che con token fresco tutto funziona (org-switcher con le sue 7 aziende, tabella inventario piena, 3 FAB). Causa = bug PRE-ESISTENTE inapi.js: un401su/auth/refresh(refresh scaduto) rientrava nel handler di refresh (la condizione non escludeva l'endpoint auth) → ricorsione infinita,doRefreshToken()non ritorna mai →logout()/redirect a login non scatta → pagina resta "--". Scenario tipico del "torno dopo >2h" (access scaduto + refresh non valido). Fix: escludi/auth/refresh+/auth/logindall'auto-refresh + un 401 senza refresh disponibile falogout(). Ora un utente che torna con sessione morta va a login (ri-accede). Cache-busterapi.js ?v=20260627usu 41 pagine. Metodo diag: minti un token conexpnel passato (jti+active_sessions) e riproduci con puppeteer (pipe:true); chiave org localStorage =nis2_org_id. Mail a Massimo (+ copia a Cristiano, 2×201) in parole semplici: come rientrare (F5 → login → riaccedi), dove trova "Seleziona azienda"/inventario/FAB segnalazioni rosso, e consiglio di installare la PWA come app Windows ("Installa app"). Follow-up chiesto da Cristiano: rendere il rientro automatico (silent re-auth) così l'utente non veda nemmeno il login.
Commit di giornata (in ordine): b0fc190 (foundation+pilota), a4150b5 (flotta 32 pagine), 58b4bdb (allineamento), 9971fbc (context), a223923 (ARIA org-fix), 95e77a6 (i18n titoli), a71d1a0 (spotlight+CSP), e15e1b8 (context), 64e10b1 (v1.25.0+SW), 41476c8 (api.js sessione scaduta). Tutti pushati. Sessioni/token QA eliminati; working tree pulito.
2026-06-24 — 🟢 SESSIONE LUNGA: Vocea scaffold + segnalazioni Massimo + Agile=studio + UPGRADE ARIA (4 fasi) + avviso tester
Multi-tema. Memorie: project_aria_chat_wiring, project_ticketing_reporter, project_vocea_whistleblowing_integration, project_agile_tenant.
A. Vocea (whistleblowing zero-knowledge) — scaffold DORMIENTE
Vocea = white-label di nexus-whistleblowing-ms (AgileHub, NON voce/TTS). Decisione: sostituire il WB interno (plaintext) — Modalità C, no crypto re-impl. Scaffold committato (4e2e56e) + fix post-smoke (d45bd47): VoceaService.php (client X-API-Key api.vocea.cloud), mig 063 (NON applicata), config VOCEA_* (off di default), WhistleblowingController proxy/channelStatus. Smoke VIGILE/Vocea ha trovato 2 bug → corretti: tenantInfo rotta NON versionata /api/wb/tenants/{slug}/info; portalUrl=/{locale}/segnala/{slug}; vault namespace = tier1__nis2-app__vocea. BLOCCO a monte: deploy MS su api.vocea.cloud (route /integrations ancora 404 per VIGILE) + API key nel vault → poi attivare (mig 063 + VOCEA_ENABLED=true + slug org).
B. Segnalazioni Massimo Tagliavini — tutte RESOLVED
Le segnalazioni utente sono ticket AgileHub (ticket-ms 4213, tenant 7, product NIS2), non feedback_reports. Bug diagnosticato da VIGILE: JWT NIS2 senza email → caller_email vuoto → invisibili in «Le mie richieste» (fix common.js getMe + backfill VIGILE). GOTCHA query ticket-ms: serve header x-tenant-id: 7; GET /tickets/{id} include messages[]; usare GET /tickets/my?callerEmail=…. Io ho chiuso #424 (smoke ARIA OK) → i 10 ticket di Massimo ora tutti RESOLVED. Mail riepilogo inviata a m.tagliavini@ (201).
C. "Agile Technology" = studio di consulenza che gestisce sé stessa (commit 3b9f30a)
Consolidamento (no duplicati): consulting_firm 1 anagrafica REALE (P.IVA 07776161213, sede legale Roma Piazza di Campitelli 2, PEC agile.software@pec.it); org 996003 (Nuova Agile) → consulting_firm_id=1 + firm_org_assignments (prima azienda auto-gestita); 4 admin con consulting_firm_id=1 (Benassati 4 consulente, Silvia 103 presidenza@, Tagliavini 326, Fattori 330). Seeder application/cli/build_agile_consulente.php (idempotente, rollback nel file). Documenti SGSI già su 996003 (12 published).
D. UPGRADE ARIA — 4 fasi A→D (lamentela "AI non allineata con help + non vede i dati")
- A (
bafc305) Allineamento Help↔ARIA:help.js getContextText/detectPageId→common.jsinviapage_id+page_help→AIService::askWithRaginietta «GUIDA CONTESTUALE DELLA PAGINA» (fonte unica help.js). - D (
4252904) Puntatore guida:public/js/aria-guide.js(NUOVO) +data-navin common-bi.js → bottone «📍 Mostrami dov'è» con spotlight sulla voce di menu. - B (
fe7d055) ARIA data-aware:AIService::orgDataSnapshotBlock(score/rischi/incidenti/asset/fornitori/NC/formazione/policy) iniettato SOLO seorg_data_ok(AiController verifica membershipuser_organizations, super_admin bypassa; scope org → no leak cross-org). - C (
a982797) Voce naturale:AiController::tts()+ routePOST /api/ai/ttsproxy anexus-voice-ms(172.21.0.1:4215, voce "Sarah") +speak()/toggle 🔊 in common.js. Smoke: voce-ms da nis2-app 200 audio/mpeg. Riferimento: trpgdocs/changes/CHANGE_aria_voice_pointer.md(ALLTAX non leggibile dal devenv). Cache-buster common.js→20260624g/help/common-bi. php-fpm reloadato.
E. Altro
- Cron supervisore NIS2 → ogni 90 min (
5 0-23/3+35 1-23/3, TEMPORANEO, backup.nis2-supervisor.bak-17h4-20260623). - Avviso tester inviato (4 destinatari, 201) — voce/guida/dati ARIA + segnalazioni risolte + Ctrl+F5.
- Memoria: nuovi
project_ticketing_reporter,project_vocea_whistleblowing_integration,reference_db_gotchas; aggiornatiproject_agile_tenant,project_aria_chat_wiring; MEMORY.md compattato (251→37 righe).
Problemi aperti / prossimi passi
- Vocea: attendere deploy MS + API key vault → attivare (mig 063 + flag + slug org pilota da presidenza).
- ARIA polish: regola "non citare nomi file .html"; smoke in-app voce+puntatore (browser, Ctrl+F5).
- Cron 90 min temporaneo → riportare a 4h/oraria quando cala l'ondata.
- VIGILE: implementare sentinella OPEN-fermi (ok dato); #384 orfano lasciato (no attribuzione affidabile).
- mig 063 da applicare all'attivazione Vocea.
2026-06-20 — 🟢 SESSIONE LUNGA: completamento UI V2 + cliente "Nuova Agile" end-to-end + feature gestione documentale ISMS
Sessione molto lunga e multi-tema. Riepilogo per blocchi. Memorie: project_client_nuova_agile, project_isms_module, project_bootstrap_italia_rollout.
A. Completamento UI V2 (Bootstrap Italia/AGID) — v1.23.10 (commit 7ad5d68, 72d82d9)
Migrate a BI le ultime pagine: architecture.html + workflow.html (app-page: CSS BI + bundle JS + common-bi.js per sidebar V2; workflow ha cablato anche l'help "?"); integrazioniext.html + mktg-api-doc.html (doc pubblici: solo CSS BI). Rimossa setup-org.html (morta). Scoperto: il funnel login/register/onboarding era GIÀ su BI (le pagine pubbliche caricano la CSS BI ma NON common-bi.js perché senza sidebar → NON classificarle con common.js-vs-common-bi.js). Corretto docs/kb/V2_UI_NAVIGAZIONE.md (era stale: descriveva il vecchio approccio -bi.html ABBANDONATO; BI_PAGES ora mappa ogni pagina a sé) — commit f9b2c9d. Corrette 3 memorie stale: supplier portal (è COMPLETO/LIVE), allineamento TRPG (5 fasi COMPLETATE), ui_v2 dark/Inter (SUPERATA: il V2 è BI).
B. Cliente "Nuova Agile Technology srl" (org 996003) — creato live end-to-end
Software house cloud-only, modello work-from-anywhere. Persone = account ESISTENTI (Garretto 103/presidenza@, Tagliavini 326, Fattori 330/s.fattori@) collegati via user_organizations (NON duplicati). Seed idempotenti application/cli/build_nuova_agile_p1..p9.php (eseguiti via SSH host→docker exec nis2-app php):
- p1: org + classif. NIS2 (important, sub_threshold_candidate/preliminary) + organigramma 8 ruoli.
- p2: 12 asset (cloud, NIS2-rilevanti) + 4 fornitori + 10 rischi + gap analysis (45%).
- p3a: ISMS model #3 (flag cloud true) + SoA 111 (93/7/11) + isms_roles · p3b: 31 documenti SGSI (Manuale/politiche/procedure/istruzioni, AI-redatti fonti-certe).
- p4: 15 piani di controllo + 5 corsi. ⚠️ Classif. "importante" = autovalutazione preliminare (sotto soglie; caveat nel Manuale).
C. Simulazione audit + bonifica (combinato ISO 27001 + NIS2)
AUD-001 (internal_audits): 21 voci checklist + 5 NC (1 maggiore: determinazione applicabilità NIS2 non formalizzata; 4 minori). Bonifica (p5): 10 trattamenti rischio + 11 formazioni completate + 15 esecuzioni controlli + SoA avanzata + DPO interim + 5 CAPA → chiuse 3 NC operative; restano aperte 2 di governance (decisioni umane). Gap analysis → 80%.
D. Login Silvia (presidenza@)
SSO_MODE=local → login usa password LOCALE. Impostata password locale user 103 ("Silvia1978!"); regge perché la sua identità SSO centrale è password_version=1 (il cron sync agisce solo su password_version>1 → la salta). Test login API: 200 OK.
E. FEATURE PRODOTTO: Gestione documentale ISMS (ISO 27001 cl.7.5)
- v1.24.0 (mig.060, commit
68694a0): ciclo di vita Bozza→Revisione→Approvato→Pubblicato→Archiviato +isms_document_versions(snapshot); 8 endpoint/api/isms/documents/{id}/(submit|approve|publish|reject|archive|newVersion|versions|word); export Word .doc (blob-fetch); UI inisms.js(badge/pulsanti/storico, IT/EN inline). - v1.24.1 (
d29195b): download UNICO set (/api/isms/documentsWordAll, copertina+indice+page-break) + editor in-app (contenteditable+toolbar+toggle HTML;getDocument; guard NOT_EDITABLE sui pubblicati). - v1.24.2 (mig.061,
e245e76): PERMESSI DOCUMENTALI profilabili per ruolo (isms_doc_permissions,requireDocCapability(), GET/PUT/api/isms/docPermissions, UI matrice insettings.htmltab "Permessi documentali"). ⚠️switchTabusa INDICI intabMap. - v1.24.3 (
7b8a604): help + i18n matrice IT/EN + ingest KB AIdocs/kb/GESTIONE_DOCUMENTALE_SGSI.md(Qdrantnis2_kbSYSTEM, ricercabilità verificata).
F. Modello agile/work-from-anywhere + consolidamento documentale (Nuova Agile)
- p7 (
6ca78f9): recepito work-from-anywhere (no sede, dispositivi dedicati, separazione privato/lavoro, posta aziendale, key escrow chiavi cloud). 5 doc agili PUBBLICATI + scope SGSI riscritto + 4 rischi R-011..014. (Ricerca web: confermato ISO 27001 lean/agile per startup cloud.) - p8 (
c05520c): CONSOLIDAMENTO 36→12. I 36 v1.0 fusi per tema (subagent dai contenuti reali) in 12 doc v2.0 PUBBLICATI (Manuale + 6 politiche + 5 procedure); 36 originali ARCHIVIATI. Gotcha: idempotenza deve ignorare gli archiviati (collisione titolo procedura incidenti). - p9 (
27bbed3): allineati i riferimenti incrociati nei 12 (rename "Politica Generale", rinvii→"Governance del SGSI", istruzione hardening assorbita, sez.8 Manuale sui 12 doc) + snapshot v2.0. Auto-referenze interne minuscole lasciate.
Stato finale doc Nuova Agile: 12 PUBBLICATI v2.0 + 36 ARCHIVIATI v1.0; 14 rischi; SoA 111.
G. Coda sessione: task #8/#9/#10 + determinazione NIS2 + chiusura NC Nuova Agile
- #9 (commit
3b1d909): re-ingest innis2_kbdella guida UI V2 CORRETTA (7 chunk, rimossi i chunk stale-bi.htmldel 12/6). Scriptapplication/cli/ingest_v2nav_kb.php. - #10 (mig.062, v1.24.4, commit
14c4c2a): version-provenance segnalazioni —feedback_reports+=client_surface/client_version/client_build; FeedbackController+Service li salvano;feedback.jsli invia (surfaceweb-v2+ version/build da version.json). NB: feedback.js è DORMIENTE (rimosso dall'iniezione 14/6) → plumbing pronto. - #8a AI consulente normativo: ✅ verificato (
/api/ai/ask→askWithRag cita art.25 D.Lgs.138/2024, 24h/72h/1mese, 5 fonti). #8c chiave mktg: ✅ moot (api_keysVUOTA). #8b (commit05a1436, v1.24.5): i18n EN modulo Fornitori —supplier-portal.html(esterno OTP) bilingue self-contained (dict inlineSPI18N+togglesp_lang);supply-chain.htmlesteso (5→25 data-i18n+97 I18n.t); i18n.js +82 chiavisp.*(→120). - Determinazione NIS2 Nuova Agile (p10, commit
51dc757): dati Direzione fatturato 0,9M€(2026)/1,4M€(2027) → SOTTO-SOGLIA; cloud provider (Allegato I) ma cap dimensionale → NON obbligata per legge; la Direzione DELIBERA l'adozione VOLONTARIA integrale come "importante" (org voluntary_compliance=1, designation_basis=voluntary, turnover=900k; Manuale sez.11 'Caveat'→'Determinazione'). NCR-9001 maggiore CHIUSA. - P.IVA + DPO (p11): P.IVA
07776161213(vat+fiscal); DPO esterno Cristiano Benassati (user 4) nominato (org_role+isms_role+Manuale+membership auditor). NCR-9005 CHIUSA → TUTTE LE 5 NC CHIUSE (0 aperte). - Sedi + anagrafica completa (p12): sede legale Roma (Piazza di Campitelli 2, 00186) in
address/city; sede operativa Modena (Strada Scaglia Est 15, 41121) in mission; scope SGSI reso preciso (sedi registrate + modello work-from-anywhere). Anagrafica Nuova Agile COMPLETA. - Escalation Simon (unico residuo, in carico a Simon): mail inviata a
s.fattori@agile.softwarecon le richieste ACN aperte → (1) requisiti soggetti "importanti" 92 vs 87 (prodotto usa 37 misure/92 req. danis2_sources.phpper Det.164179/2025 All.1; ACN ufficiale 87 — divergenza già annotata nel codice come "da riconciliare"); (2) conferma date Determinazioni ACN 164179 (14/4/25), 333017 (22/9/25, superata), 379887 (vigente, eff. 31/12/25). Alla risposta → aggiornareapplication/config/nis2_sources.php.
Note accesso/deploy
Host: chiave SSH effimera .ssh-temp/agilehub-host-temp (host dfm-dev/172.18.0.1). PROD = php-fpm HOST → reload .php = systemctl reload php8.4-fpm + USR2 nis2-app. Git push diretto dal devenv (helper vault). Prossima mig = 063.
2026-06-18 — 🟢 Hardening integrità DB (mig.058) — v1.23.1
Richiesta utente: "analizzare DB e chiavi per garantire integrità db". Esteso l'audit chiavi della mig.039 (12/6, fermo a ~tab.039) ai moduli 040-057 non ancora coperti. Memoria: project_db_integrity.
Stato base sano: 103 tabelle, tutte con PK, tutte InnoDB. Sonda live read-only nuova: application/cli/db_integrity_probe.php (PK/engine/UNIQUE/FK/orfani + pre-check duplicati (org,code); etichetta audit_logs come by-design, non bloccante).
mig.058 applicata (runner guardato application/cli/migrate_058_integrity_keys.php, idempotente, 15/15 OK, re-run = 12 SKIP):
- TIER 1 additivo (0 dup/0 orfani verificati): +4 UNIQUE [
internal_audits/management_reviews(org,code),kb_uploaded_documents.qdrant_doc_uuid,whistleblowing_reports.anonymous_token(dropidx_tokenridondante)] + retry-on-1062 nei controllerInternalAudit/ManagementReview(allineati aperiodic_controls); +6 FK [consulting_firm_idsuorganizations/users/kb_uploaded_documents→consulting_firms;isms_soa.linked_control_id→compliance_controls;isms_documents.linked_policy_id→policies;management_review_decisions.capa_id→capa_actions, tutte SET NULL]. - TIER 2 bonifica (confermata dall'utente, backup pre-DELETE in
.backups/mig058_pre_cleanup_20260618-075418.sql): cancellate 9 righe stalefirm_org_assignments→org 126-129 (eliminate nella pulizia demo 12/6; chiude il deferred-b mig.039) + 7 sessioni scaduteactive_sessions→org inesistenti, poi 2 FK CASCADE. - FK totali 196 → 204. Verdetto sonda post-fix: 0 problemi bloccanti (resta solo
audit_logsby-design: trail immutabile mig.006). Smoke prod: api-status 200, controller list 401 (caricano OK post-reload).
By-design lasciato: audit_logs (no FK, immutabile), sentinel supplier_categories.org=0 (deferred-a mig.039), colonne polimorfiche (object_id/entity_id/...) e cross-DB (sso_identity_id/lg231_*).
Deploy: reload opcache systemctl reload php8.4-fpm (PROD=host) + USR2 nis2-app. version.json → 1.23.1. Prossima mig = 059. Pulizia: rimossi 4 file * copy.html residui in docs/nis2/.
2026-06-17 (seguito) — 🟢 Modulo CONTROLLI PERIODICI (4° modulo ISO-readiness, §9.1/A.8.16) — v1.23.0, mig.057, commit 4fdeae7
Richiesta utente: "oltre al modulo audit un modulo controlli con frequenza e generazione nc o ac" + "fai analisi anche di questo modulo / documentati con altri esempi" → analisi CCM (Continuous Control Monitoring, pattern Eramba/Drata/Vanta) in docs/DESIGN_CONTROLLI_PERIODICI.md. Memoria: project-iso-readiness-modules (punto D).
Cosa fa: registro dei controlli RICORRENTI (owner=ruolo organigramma, frequenza giornaliero..annuale o personalizzata in giorni, metodo, controllo SoA/NIS2 collegato) + registro esecuzioni = EVIDENZA (data/esito/note). La scadenza avanza ad ogni esecuzione; da esito non conforme/parziale → genera NC o NC+azione correttiva (riuso NCR/CAPA, nuova source='monitoring'). Scheda+storico stampabile; scadenze nel Calendario unico.
- mig.057:
periodic_controls+periodic_control_executions(FK CASCADE, UNIQUE (organization_id, code)); ALTERnon_conformities.source+= 'monitoring'. DDL autoritativa = seederapplication/cli/seed_periodic_controls.php(idempotente; il.sqlè solo doc-record e NON ha lo UNIQUE). PeriodicControlController(slugperiodic-controls) +controlli-periodici.html+CalendarController::srcPeriodicControls(typeperiodic_control).
Costruito + verificato con FLOTTE (richiesta "agenti al massimo"): build + flotta di verifica avversariale (18 agenti, 5 dimensioni → skeptics per finding) → 9 finding confermati, TUTTI corretti:
- HIGH advance() overflow:
strtotime('+1 month')saltava febbraio (31/01→03/03) → riscritto conDateTimeImmutable+ clamp ultimo giorno valido (mensile/trim/sem/annuale; gg/sett/custom invariati). Verificato in prod: 31/01→28/02, 30/11 trim→28/02, 29/02 bisestile annuale→28/02. - HIGH review_schedule sync mai implementato → rimossa la cleanup morta in
delete()(il calendario leggeperiodic_controlsdiretto, non usa review_schedule). - Pre-esistente sanato:
CalendarController::srcReviewSchedulefaceva doppia comparsa di internal_audit/stakeholder_activity (già con sorgente diretta) →entity_type NOT IN ('internal_audit','stakeholder_activity'). - Minori: retry-on-1062 sul codice CTL-NNN;
array_key_existsper azzerare owner_role_id/next_due_date; TYPE_METAperiodic_controlnel calendario (chip/label/colore). - ⚠️ Residuo NON corretto (follow-up):
ManagementReviewController::nextCode()stesso pattern MAX+1 senza UNIQUE (race low-sev).
Smoke prod OK (org 151, poi ripulita 0 residui): CRUD, esecuzione+avanzamento, NC/NC+AC, report 200, calendario; overflow date verificato; UNIQUE uk_pctl_code presente; azzeramento next_due via update.
Routine di chiusura completata: help.js + i18n IT/EN (già in build) + KB SYSTEM uuid 70e850da + email tester (stile prodotto). Cache-buster ?v=20260631, SW nis2-shell-v1.23.0. Deploy: seeder via SSH→docker exec nis2-app php, reload systemctl reload php8.4-fpm host (PROD = php-fpm host) + USR2 container. Commit+push diretto dal devenv. Prossima mig = 058.
Inoltre 17/6: inviata a Simon mail di riepilogo chiusura Epic C (C1–C5 tutti LIVE; chiede conferma 92↔87 requisiti importanti + data esatta Det.ACN 333017/2025).
2026-06-17 — 🟢 ISO-readiness: 3 moduli LIVE (Audit interni §9.2 + Riesame Direzione §9.3 + Calendario scadenze) — v1.22.0, mig.055-056, commit 79ca72f
Nati dalla valutazione ISO 27001 per l'audit di Agile (checklist in docs/Agile 27000/, valutazione docs/Agile 27000/VALUTAZIONE_COPERTURA_NIS2AGILE_vs_ISO27001.md): mancavano gli output gestionali 9.2 e 9.3. Design: docs/DESIGN_AUDIT_INTERNI_RIESAME_DIREZIONE.md. Memoria: project-iso-readiness-modules.
Costruiti con FLOTTE PARALLELE (richiesta utente "agenti al massimo"): workflow build (3 agenti, file non sovrapposti, ritorno patch per i file condivisi che ho integrato io) + workflow di verifica avversariale (28 agenti, 4 dimensioni × 2 lenti).
- A — Audit interni (§9.2, mig.055):
internal_audits+internal_audit_items;InternalAuditControllersluginternal-audits;internal-audits.html. AUD-NNN, checklist pre-popolata (clausole 4-10 + Annex A dal SoA), esiti per riga, apertura NC→NCR/CAPA, report stampabile, data→calendario (ENUM review_schedule +=internal_audit). - B — Riesame Direzione (§9.3, mig.056):
management_reviews+management_review_decisions;ManagementReviewControllerslugmanagement-reviews;management-review.html. RD-AAAA-NN,gatheraggrega gli input dai moduli, decisioni, approve→snapshot congelato, verbale stampabile. - C — Calendario scadenze (NESSUNA mig):
CalendarControllerslugcalendar(events/summary), aggregatore sola-lettura di tutte le scadenze;calendario.html(griglia+lista+filtri+deep-link).
Verifica flotta → 9 finding, TUTTI corretti: MAJOR gatherRisks (usava risk_treatments.organization_id inesistente → JOIN su risks); nextCode numerico; footer report audit "ISO buona prassi non obbligo"; help 24→25 clausole; ARIA tab/calendario + focus modali + tasti celle; rimossi 2 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.
Routine di chiusura completata (help.js 3 sezioni + i18n IT/EN + KB SYSTEM uuid a04c8564 + email tester Fattori/Tagliavini/Mascagni, stile prodotto via relay). Cache-buster ?v=20260630, SW nis2-shell-v1.22.0. Prossima mig = 057.
Gap ISO residui (NON software, per la direzione): controlli tecnici A.7/A.8 sull'infrastruttura reale + alcuni atti formali; ISO resta volontaria (gli obblighi sono NIS2/D.Lgs.138/2024).
2026-06-16 (sera) — 🟢 EPIC C COMPLETO: C5 Stakeholder LIVE (C5.1 + C5.2 + hardening) — commit c782aa5→b917d2d, v1.19.0→1.21.1
Chiuso l'ultimo punto di Simon. Memoria: project-epic-c-simon. Tutto deployato in prod, smoke verde, committato+pushato (push diretto dal devenv).
C5.1 — Registro Stakeholder + matrice di Mendelow ✅ (commit c782aa5, mig.051, v1.19.0)
- Decisione di design (si discosta dall'handoff): tabella dedicata
stakeholders(NON colonna susuppliers): gli stakeholder interni non sono fornitori. Interni→org_roles; esterni→link opzionale asuppliers(no duplicazione). - Config:
cfg_stakeholder_types(30 Stak.01-30 + org-added da Stak.31, code=PK) +cfg_stakeholder_quadrants(4, Q4 potere 0-2 = refuso corretto) + m2mstakeholder_procedures.StakeholderController(slugstakeholders); quadrante calcolato a read-time; anti-IDOR completo.stakeholders.html= registro + matrice Mendelow SVG dependency-free. 🐛 voce "Stakeholder" mancava incommon-bi.js(sidebar V2) → aggiunta.
C5.2 — Attività, questionari, calendario, portale esterno ✅
- Decisioni utente: portale esterno self-service ORA (magic-link, email off) + modello template dedicato leggero.
- C5.2a (
a5ff29e, mig.052, v1.20.0): questionari tipo (stk_questionnaire_templates, kind questionnaire/read_ack) + m2m a procedure/misure/requisiti;stk_activities+stk_activity_targets+stk_activity_procedures; assegnazione by_code/individuale; calendario via review_schedule esteso (ENUM += 'stakeholder_activity').StakeholderActivityController(slugstakeholder-activities);stakeholder-activities.html. - C5.2b (
4f386fa, mig.053, v1.21.0): portale esternoStakeholderPortalController(slugstakeholder-portal, non-JWT, token magic-link): access/respond/acknowledge/comment/attachment;stk_activity_responses+stk_activity_comments; allegati suevidence_files.stk-portal.htmlpubblica. Sotto-dashboard feedback nel dettaglio attività.
Verifica finale + hardening ✅ (commit b917d2d, mig.054, v1.21.1)
- Flotta di verifica (Workflow, 40 agenti, 4 dimensioni + verifica avversariale a 2 lenti): 18 finding (5 major/9 minor/4 info), tutti corretti.
- Fix principali: XSS allegati chiuso (blocklist→ALLOWLIST estensioni, no html/svg/js same-origin) + nome file random; scadenza magic-link (
token_expires_at, 410) + rate-limit portale + guard scritture su attività chiuse; send() non azzera i destinatari già responded/acknowledged; assign individuale "replace"; update conserva assign_mode; editor opzioni domande a scelta; etichette stato i18n; risposte inline (no alert); ARIA; escAttr portale; voce sidebar in common.js. - KB SYSTEM ingerita (uuid
2c7ec47b). Cache-buster?v=20260629, SWnis2-shell-v1.21.1.
Follow-up open-items (commit 3051983, v1.21.2)
- ✅ Upload allegati RISOLTO. Causa: il prod non è servito dal container nis2-app ma da Apache host → php-fpm HOST (
/run/php/php8.4-fpm.sock, DocumentRootpublic/);public/uploadshost eragit:gitcon ACL che concedeva write solo agit, NON awww-data(utente fpm host) →move_uploaded_filefalliva. Fix host (NON in repo, da ri-applicare se il dir viene ricreato):setfacl -R -m u:www-data:rwx -m d:u:www-data:rwx /var/www/nis2-agile/public/uploads. Collaudato: upload interno+portale .pdf 201, .html→422. Risolve anche evidence_files/visure (rotti per lo stesso motivo). ⚠️ Nota architetturale: il prod gira su php-fpm host (opcache.validate_timestamps=On → prende i .php in automatico); per reload immediatosystemctl reload php8.4-fpmsull'host (ildocker exec nis2-app kill -USR2 1reload SOLO la dev-API del container). - ✅ Fonti ACN aggiornate (
nis2_sources.php, verifica nis2-expert): 333017/2025 = 22 settembre 2025, marcata STORICA/superata; aggiunta 379887/2025 VIGENTE (efficace 31/12/2025); 164179/2025 (14 apr 2025) confermata vigente (base misure/requisiti). Date-giorno marcate[da confermare sul testo ufficiale]. - 🟢 #2 — 92 vs 87 requisiti: DECISO da utente "prevale Simon" (resta 92). Aggiunta NOTA DI PROVENIENZA in
nis2_sources.php(prodotto usa 92 = file referente; ACN ufficiale 87; divergenza nota/accettata). Numero NON cambiato. (Resta incoerenza con altri punti del codebase che usano 87 — accettata.)
Chiusura open-items (commit 69873e2, v1.21.3) — "fai il massimo"
- 🟢 #7 — invio email AGGANCIATO e pronto al go-live (GATED):
send()invia il magic-link viaEmailServiceSOLO seEMAIL_SENDING_ENABLED=true. Oggi è false (default, non in.env) → zero invii (smoke prod:email_sent=0). Al go-live basta settare il flag. Link assoluto viaAPP_URL. I link restano sempre esposti per condivisione manuale. - 🟢 #8 —
EMAIL_MS_URLspostato sul path internohttp://172.21.0.1:8081/api/emails(host.env, fuori repo). Verificato 400 conX-Internal-Key= funzionante (pre edge-strip). Rollback: rimettere l'URL pubblico. (Inattivo finché email off.) - 🟢 #9 — TLS DB: DONE (sign-off VIGILE, commit
d583acf). Pre-equip PHP-CA del prodotto fatto (app in TLS 1.3,cipher=TLS_AES_256_GCM_SHA384, CAapplication/config/db-ca.pem). EnforceREQUIRE SSLsunis2_user@'%'già attivo dall'11/6 (applicato da VIGILE nel cutover container;ssl_type=ANY) — confermato indipendentemente da NIS2-side conSHOW CREATE USER. Il mio precedente "non enforced" era falso allarme: in MySQL 8.0SHOW GRANTSNON mostraREQUIRE(soloSHOW CREATE USER). Ciclo: richiestaOUTGOING_TO_AGILEHUB_2026_06_16→ confermaINCOMING_FROM_AGILEHUB_2026_06_16_tls_enforce_gia_attivo.md. CLAUDE.md testata → DONE. - 🧭 Scoperta architetturale (importante): il PROD
nis2.agile.softwareè servito da Apache HOST → php-fpm HOST (/run/php/php8.4-fpm.sock, DocumentRootpublic/, utentewww-datahost), NON dal containernis2-app(che serve solo la dev-API). Quindi: reload .php prod =systemctl reload php8.4-fpmsull'host (USR2 al container = solo dev-API; host havalidate_timestamps=On);public/uploadsscritto da www-data host (da cui il fix ACL). Memoria: project-prod-topology-host-fpm. - ⏭️ #6 nginx Content-Disposition /uploads/: skip motivato (allowlist chiude la XSS; forzerebbe download dei PDF). Applicabile via
restart(nginx.conf bind-mount) se richiesto. - 🧪 #4 collaudo E2E browser C1–C5: resta task umano dei tester. Best-effort fatto: render-smoke pagine C5 (200 + marker DOM OK). QA visivo umano comunque raccomandato.
Prossima migrazione = 055. ⚠️ Promemoria infra (non-repo, ri-applicare se ricreati): setfacl su public/uploads; EMAIL_MS_URL interno in .env.
2026-06-16 — 🟢 EPIC C (segnalazioni Simon C1–C5): C1, C2, C3, C4 LIVE — commit d370479→30f4614, v1.18.0→1.18.4
Direttive vincolanti di Simon Fattori (C1–C5), validate con l'agente nis2-expert. File sorgente autoritativi in docs/simon/ (C1_Misure_Requisiti.xlsx, C1_Vista_MisureRequisiti.xlsx, C5_EleStakeH.xlsx). 🔑 Direttiva architetturale utente: tutto data-driven via tabelle di configurazione (niente JSON hardcoded). Memoria: project-epic-c-simon.
C1 — Elenco Misure/Requisiti (config-driven) ✅
- mig.046
cfg_nis2_ambiti(18)/cfg_nis2_misure(43)/cfg_nis2_requisiti(116)/cfg_nis2_procedure(42)/cfg_nis2_rischi(84), system-level read-only, seed daapplication/data/nis2_framework_seed.json(generato dai .xlsx, codifica intatta). Importer idempotente committatoapplication/cli/seed_framework.php. FrameworkController(/api/framework/catalog+/state), paginamisure-requisiti.html(2 viste, righe "spente" per classe, sola lettura). mig.047org_requisito_state= valutazione/stato di conformità per requisito (la valutazione parte vuota → da qui emerge la conformità). mig.048 copia 42 procedure default nel modulo Procedure al 1° accesso (transazionale + FOR UPDATE).- Importanti 37 misure/92 requisiti, essenziali 43/116 (92 dal file di Simon, ⚠️ vs 87 contati su All.1 dall'expert — da riconciliare con Simon).
C2 — «Determinazione» (non «Determina») ✅ — decisione utente bloccata.
C3 — Rimosso modulo "Analisi GAP ACN" ✅
- mig.049 drop
acn_assessments/acn_assessment_responses(erano VUOTE, 0 dati persi). Rimossi controller/pagina/acn_measures.json/routing/sidebar/api/help/i18n. RaciController ripuntato sucfg_nis2_misure. Integrazione lg231 intatta (/services/gap-analysisusaassessmentsgenerale). Tour demo step 3 ripuntato su misure-requisiti.
C4 — Inventario a 2 voci ✅
- mig.050
cfg_inventory_voci(2: ID.AM-01/02) +inventory_subclassi(12 default + org),assets.voce_code/subclass_id. 20 asset migrati.AssetController+assets.html(Voce+Sottoclasse, "+ aggiungi").asset_typeresta legacy. - 🐛 2 bug pre-esistenti risolti: apostrofo non escapato (
l'inventario) rompeva il JS della pagina Inventario;common-bi.js?v=20260618mai bumpato → la sidebar V2 non mostrava "Misure e Requisiti".
Verifica + qualità
- Flotta di verifica (workflow 14 agenti su C1) → sicurezza pulita, fedeltà dato perfetta (codifica Simon intatta); 6 finding (1 major + 5 minor) tutti corretti (commit
45e6c35). - Migrazioni: prossima = 051. Help/i18n/KB aggiornati per ogni punto. Cache-buster
?v=20260625, SWnis2-shell-v1.18.4.
⏳ Resta
- C5 — modulo Stakeholder completo (NON iniziato): 30 tipi
Stak.01-30config, anagrafica + link organigramma/procedure, doppia valutazione potere/interesse 0-5, matrice Mendelow (🔧 refuso Q4 = potere 0-2), attività/questionari+feedback collegati a procedure e misure/requisiti m2m, calendario. Da fare a fasi (C5.1 anagrafica+matrice, C5.2 attività/questionari/calendario). Mendelow = best practice, non obbligo → etichettare (fonti-certe). - Flotta di verifica finale + help/trad/KB: l'utente l'ha chiesta "alla fine di tutto il lavoro" (dopo C5).
- E2E browser delle nuove UI (C1–C4): da far esercitare ai tester.
2026-06-15 (notte) — 🎉 A4 COMPLETA (5/5): RACI + Scadenziario + Stakeholder LIVE — commit b4d47d5→64866be, v1.17.2
Chiuse le ultime 3 fasi di A4 in background (workflow ultracode: scout → build backend+frontend file-disgiunti → review adversariale 3 lenti → sintesi). A4 ora completa (5/5 LIVE). Tutto pushato (HEAD 64866be), working tree pulito.
4.3 — Matrice RACI (commit b4d47d5, mig.043, v1.17.1)
- Migration 043:
raci_assignments(ruolo×oggetto;object_typeENUM inventory/procedure/risk/supplier;object_idpolimorfico = NESSUNA FK, esistenza validata app-layer org-scoped inRaciController::assertObjectExists→ anti-IDOR) + link m2mprocedure_inventory/procedure_risk/inventory_risk/risk_measure(measure_codevalidato suacn_measures.json, no FK). APPLICATA su prod. RaciController(routeraci):matrix,assign/unassign,objects,links/link/unlink.raci.html+raci.js(griglia ruolo×oggetto, celle R/A/C/I).- ⚠️ Bug runner: PDO::exec rompe sui commenti
--con apostrofo → runner aggiornato a strippare le righe--.
4.4 — Scadenziario centralizzato (commit c5b00cc, mig.044)
- Migration 044:
review_schedule(entity_type role/skill/inventory/procedure/risk/supplier/measure/custom; status ok/due/overdue calcolato live). APPLICATA su prod. ReviewScheduleController(routereview-schedule):list/create/update/delete/complete(avanza next_review_date di frequency_months)/sync(importa scadenze sparse da policies.next_review_date / compliance_controls / risk_treatments).review-schedule.html. Ancoraggio GV.PO-02/GV.SC-07/PR.AT/DE.CM.
4.5 — Stakeholder estesi (commit c5b00cc, mig.045)
- Migration 045:
ALTER suppliers ADD stakeholder_type ENUM('supplier','customer','partner'). ⚠️ Era stata scritta conDELIMITER/stored-procedure (NON runner-safe) → review l'ha bocciata, riscritta ad ALTER bare + runner con try/catch 1060/1061. APPLICATA su prod. SupplyChainController::stakeholderMap()+stakeholders.html. Riusa Supply Chain (no doppione). GV.SC-02 (fornitori + clienti + partner).
Help + i18n + KB + release
help.js: sezionireview-schedule+stakeholders(+ fix wording GV.SC-04/05).i18n.js:nav.review_schedule/nav.stakeholdersIT/EN. Sidebarcommon.js.- KB Qdrant SYSTEM ingestata: "Scadenziario centralizzato e Mappa stakeholder (A4 4.4/4.5)" (doc_uuid
15d7b3ff). - Cache-buster
?v=20260620(37 HTML).version.json→ 1.17.2. SW cache bump.
Verifica
- Smoke su prod (fpm reale):
/raci/matrix,/review-schedule/list,/supply-chain/stakeholderMap→ 200; pagine 200; voci sidebar presenti. Review adversariale: 0 finding critical/major (045 DELIMITER corretto in sintesi). ⚠️ E2E browser dei nuovi moduli NON fatto da me → demandato ai tester.
Email tester
- 2 email ai 4 tester (benassati/tagliavini/fattori/mascagni, HTTP 201): (1) ringraziamento "segnalazioni Fattori accolte + aggiornato", (2) "punto del progetto" (A1/A2/A3/A4 implementate + invito a collaudare i 5 nuovi moduli via FAB). Risposte → cristiano.benassati@gmail.com.
Prossimo passo
- Raccogliere il feedback E2E dei tester sui nuovi moduli (organigramma/competenze/RACI/scadenziario/stakeholder + onboarding). Supervisore autonomo: osservazione →
implemented~21/6. Opzionali: rotazione chiave Anthropic; PDF integrale D.Lgs. per KB; cleanup dead-code classificazione.
2026-06-15 (sera-2) — ✅ A4 Fase 4.2 IMPLEMENTATA (Competenze + gap + alert→azione) — commit 5368b0a, pushato, v1.17.0
Implementata la seconda fase di A4: le Competenze (skill, requisiti per ruolo, competenze possedute, gap, azione). Workflow ultracode: mapping in parallelo (5 reader) → implementazione → review adversariale (3 dimensioni, 0 finding) → release.
🔴 Scoperta del mapping (landmine evitato)
La tabella delle azioni NON è corrective_actions (nome nel design/memoria, INESISTENTE): è capa_actions (mig.004), figlia obbligatoria di non_conformities (NCR) via ncr_id NOT NULL, senza colonna source_type. Design doc + memoria corretti.
Backend
- Migration 042 (
docs/sql/042_competences.sql) + runner aggiornato. 4 tabelle additive/idempotenti:skills(org-owned o globaliorganization_id NULL),role_skills(UNIQUE role+skill),user_skills(UNIQUE org+user+skill,acquired_via_course_id),skill_course_map. APPLICATA su prod (nis2-db v8.0.45 TLSv1.3; skills 2FK, role_skills 4FK, user_skills 5FK, skill_course_map 3FK). CompetenceController(routecompetences):catalog, skills CRUD,roleSkills/setRoleSkill/removeRoleSkill,userSkills/setUserSkill/removeUserSkill,mapCourse/unmapCourse,gapGrid(richiesto−posseduto per ruolo con titolare, corsi suggeriti, azioni aperte),openAction. Multi-tenancy + anti-IDOR + livelli 1-5; competenze globali read-only per org (solo super_admin).- alert→azione (
openAction): riusa NCR/CAPA → creanon_conformities(source='management_review',source_entity_type='competence_gap',source_entity_id=role_skills.id, severity major se gap≥2,nis2_article=PR.AT-02 se essenziale altrimenti PR.AT-01) +capa_actionsfiglia, in transazione. Anti-duplicato su source_entity_id. Zero ALTER alle tabelle esistenti.
Frontend
competenze.html(4 tab: Catalogo · Requisiti per ruolo · Competenze persone · Gap & azioni) +js/competenze.js. CTA "Assegna corso" (riuso/training/assign, nuovoapi.assignTraining) e "Apri non conformità". Bootstrap Italia V2.- Voce sidebar "Competenze" (
common.js+common-bi.js+BI_PAGES) +nav.competencesi18n IT/EN.api.js: metodicomp*.
Help + KB + PWA
help.js: guida'competences'(schede, calcolo gap, PR.AT-01/02, GV.RR-04, art.24 D.Lgs., disclaimer) + mappa pagina.- KB Qdrant SYSTEM ingestata (doc_uuid
3739398d). - PWA:
sw.jscache →nis2-shell-v1.17.0(allineamento release). - Cache-buster
?v=20260618dei 5 JS condivisi su 32 HTML.version.json1.16.0 → 1.17.0.
Verifica
- Smoke E2E su prod (fpm reale): catalogo, requisito (liv.4), competenza persona (liv.2), gap=2, openAction → NCR-766066 + CAPA-984386, anti-dup 409, mappatura corso, statici live — tutti verdi. Dati di test ripuliti dall'org tester 151 (0 residui).
- Review adversariale (workflow, 3 dimensioni security/correttezza/fonti + verifica scettica): 0 finding.
- Email alle attività inviata ai 4 tester (HTTP 201).
Prossimo passo A4
- 4.3 matrice RACI (
raci_assignments, ruolo×oggetto) + link m2m procedure/inventario/rischi (segn. 4/5/7). Poi 4.4review_schedule, 4.5 stakeholder GV.SC-02.
2026-06-15 (sera) — ✅ A4 Fase 4.1 IMPLEMENTATA (Organigramma) — commit 8540b53, pushato
Implementata la prima fase di A4 (modello relazionale, docs/DESIGN_A4_RELATIONAL.md): l'Organigramma.
Backend
- Migration 041 (
docs/sql/041_org_roles.sql) + runnerscripts/migrate-a4.php. Tabellaorg_rolesadditiva/idempotente:parent_role_id(gerarchia self-FK),holder_user_id(titolare),is_governance_body(organi amministrazione/direttivi, Art.23 D.Lgs.138/2024),description(GV.RR-02),sort_order, audit cols. APPLICATA su prod (containernis2-dbv8.0.45, TLSv1.3, 11 col, 4 FK). ⚠️scripts/edocs/NON sono montati in nis2-app (soloapplication/+public/): applicata con runner self-contained SQL-inline sottoapplication/_migrate_*_tmp.php(pattern 040), eseguito viadocker execesportandoDB_da/proc/1/environ, poi rimosso. OrgRoleController.php:list(flat+tree arricchiti),get,create,update,delete,assignableUsers. Multi-tenancy sugetCurrentOrgId(), anti-IDOR (id+organization_id), prevenzione cicli gerarchia (walk-up), holder = membro org, delete bloccato se ha figli (409). Routeorg-rolesinpublic/index.php.
Frontend
public/organigramma.html+js/organigramma.js: vista ad albero (badge governance, titolare/vacante, descrizione), editor crea/modifica/elimina con select padre anti-ciclo lato UI, statistiche, "crea struttura di base". Bootstrap Italia V2.- Voce sidebar "Organigramma" in sezione Gestione (
common.js+common-bi.jsattivo +BI_PAGES), icona sitemap,nav.org_charti18n IT/EN.api.js: metodiorgRole*(wrapper_acn).
Help + KB AI (regola "fine sessione")
help.js: guida contestuale'org'(cosa rappresenta, nodo Art.23, come si usa, fonti certe D.Lgs.138/2024 art.23 + GV.RR-02 best-practice, disclaimer no-parere-legale) + mappa pagina→help.- KB Qdrant
nis2_kbSYSTEM: ingestato doc modulo Organigramma (doc_uuid96dbafcc…, 2 chunk). RAG verificato (search aggancia GV.RR-02 "approvate dagli organi di amministrazione").
Deploy/verifica
- Cache-buster
?v=20260617sui 5 JS condivisi (api/common/common-bi/i18n/help) su 32 HTML (help.js era già a 20260616 → bump obbligatorio).version.json1.15.2 → 1.16.0 (MINOR, nuovo modulo). USR2 fatto. Smoke E2E su prod (fpm reale): login tester, CRUD, tree, anti-ciclo (422), delete-con-figli (409), cleanup — tutti verdi, org 151 ripulita.
Prossimo passo A4
- 4.2
skills/role_skills/user_skills/skill_course_map+ gap competenze (segn.3, PR.AT-01/02, GV.RR-04) + alert→azione (riusocorrective_actions). - Poi 4.3 matrice RACI (
raci_assignments) + link m2m procedure/inventario/rischi · 4.4 scadenziarioreview_schedule· 4.5 stakeholder GV.SC-02.
2026-06-15 — ✅ Segnalazioni Fattori: A1/A2/A3 IMPLEMENTATE + A4 disegnato + UNICA UI + tester
Tester onboarding
- 4 tester (Benassati, Tagliavini, Fattori, Mascagni) → password
Tester2026!, role super_admin, membership org 151/152 (no wizard), login 200 verificato. Client riusabileapplication/cli/provision_testers.php(dry-run default,--expectguard, SSO-sync-safe), commitd50955d. Mail di ingaggio a tutti e 4 (viasend-raw, concurl— python urllib bloccato da Cloudflare 1010).
Segnalazioni Simon Fattori — analisi normativa + risposta (mail → cristiano.benassati@)
- Verifica su testi ufficiali (nis2-expert + workflow): del punto 0, alcune affermazioni SMENTITE → importanti 37/87 vs essenziali 43/116 (confermati), "requisito" è termine UFFICIALE, "Determinazione" non "Determina", inventario NON solo HW/SW, gap-ACN NON si cancella. Accolte invece: citare D.Lgs. (non Direttiva), onboarding art.3 c.9, terminologia "inventario".
- ⚠️ Saga comma art.3: la citazione originale art. 3 c. 9 lett. b/c/d (criteri sotto-soglia) era GIUSTA; una "rettifica" intermedia (→c.13) è stata un'over-correction (inferenza incompleta prima del testo verbatim); recuperato il testo verbatim (reteambiente) → confermato c.9 b/c/d (c.13 = procedura individuazione). Salvato
docs/nis2/Dlgs138_art3_ambito.md. 3 mail a Fattori (reply + rettifica + chiarifica). Lezione: verificare il testo primario PRIMA di "correggere" una citazione.
A1 — citazioni obblighi → D.Lgs. (commits 121b8c2, d009341, 9fed095)
AIService (blocco fonti iniettato + classificatore incidenti) + IncidentController + gate supervisore + help.js (~36 citazioni): obblighi citano D.Lgs. art. 23 (governance) / 24 (rischio) / 25 (notifica), Direttiva solo per principi. Verificato LIVE (help.js 53 citazioni D.Lgs., 0 residui obblighi-Direttiva).
A3 — "Inventario" non "Asset" (stessi commit)
nav + titoli + i18n IT/EN + pagine assets (titoli, label, stringhe JS modali/notifiche) + guida help.js → "Inventario/beni". Tabella assets/endpoint/asset_type (tecnici) invariati.
A2 — onboarding classificazione (commits b2f47c0, c5fce7b, 717f9ac)
- Migration 040 (11 colonne
organizations, applicata+verificata prod): bilancio, autonomia,size_independent_category, 3crit_*, mission, codice etico,designation_basis,self_assessment_status. Nis2ClassificationServicev2 (nuovo, centralizzato → eliminata duplicazione Onboarding/Organization): matrice settore×dimensione + bilancio €43M, size-independent (art.3 c.5), criteri sotto-soglia (art.3 c.9 b/c/d →sub_threshold_candidate, NIENTE auto-classe), obblighi D.Lgs. art.23/24/25, esitoself_assessment_status=preliminary+ disclaimer ACN (Det.333017, DPR445, finestra 1/1-28/2). 9/9 unit + smoke HTTP.- UI wizard onboarding esteso (step2 nuovi campi + step4 3 domande/obblighi/disclaimer/badge "preliminare"), classificazione v2 lato server, markup Bootstrap Italia/AGID. JS valido, campi serviti. E2E browser del wizard = da far esercitare ai tester.
UNICA UI (commit 3c5184b)
Rimossi 28 duplicati *-bi.html (orfani post-swap, 0 riferimenti). Le pagine ufficiali (nomi senza -bi) SONO la UI V2 Bootstrap Italia. Verificato: ufficiali 200 / -bi 404 / sidebar 0-ref.
A4 — DISEGNATO (commit dbda3ef), NON implementato
docs/DESIGN_A4_RELATIONAL.md: organigramma org_roles (+ nodo governance Art.23) + matrice RACI (raci_assignments) + skill/gap-competenze/formazione + m2m procedure/inventario/rischi/misure + scadenziario centralizzato (review_schedule). 5 fasi (4.1 organigramma → 4.5 stakeholder GV.SC-02). Riuso training/policies/assets/risks/suppliers/capa; isms_roles resta per SoA SGSI.
Aperti / prossimi
- A4 da implementare (fasi 4.1→4.5) — epic multi-tabella + UI.
- Help/traduzioni/KB AI da aggiornare per A1/A2/A3 (regola CLAUDE.md impatti funzionali). [in corso a fine sessione 15/6]
- Dead-code innocuo:
getClassificationExplanation/getClassificationDetails. - PDF integrale D.Lgs. 138/2024 per KB completa (non auto-fetchabile da GU/Normattiva, serve manuale).
2026-06-14 — ✅ SUPERVISORE AUTONOMO TICKET LIVE (cron) + chiusura demo/avatar/feedback
PWA conforme AGID — v1.15.0 (sessione serale)
NIS2 reso PWA installabile su Android (Chrome) e iOS/iPadOS (Safari "Aggiungi a Home"), responsive, conforme AGID/WCAG 2.1 AA. Avviato come flotta di agenti in background, completato inline dopo riavvio del processo (gli agenti background erano andati persi).
- File nuovi:
public/manifest.webmanifest(theme#0066CCblu Italia, display standalone, orientation any, shortcuts Dashboard/Incidenti) ·public/sw.js·public/offline.html·public/js/pwa.js(register SW + banner installazione Android + hint iOS, i18n IT/EN) ·public/assets/icons/*(icon-192/512 + maskable + apple-touch 180 + favicon 16/32 +icon.svgmaster) ·scripts/gen-pwa-icons.mjs(generatore icone zero-deps, pure Node+zlib) ·scripts/inject-pwa-head.mjs(iniezione idempotente tag head). - Service worker: cache
nis2-shell-v1.15.0;/api/*network-only (no leak cross-utente su device condiviso), navigazioni HTML network-first →offline.html, asset stale-while-revalidate. Bumpare il nome cache ad ogni release. - Head: tag PWA iniettati in 77 HTML (64 + 6 admin + 7 subdir, escluso
offline.html); viewport normalizzato aviewport-fit=covermantenendo lo zoom (WCAG 1.4.4). - AGID/WCAG: Bootstrap Italia (già live), zoom+orientamento liberi, touch target ≥44px (
@media (pointer: coarse)instyle.css), safe-area in standalone. "Dichiarazione di accessibilità" obbligatoria solo per PA → opzionale, non implementata. - Rigenerare:
node scripts/gen-pwa-icons.mjs(icone) ·node scripts/inject-pwa-head.mjs(tag head, idempotente). Tutto statico → live via bind-mount, nessun USR2. - Bump
version.json1.14.1 → 1.15.0. Committato36513fa(21:09 del 14/6) e pushato su Gitea (b80bf23..36513fa, 00:38 del 15/6, via host con chiave fresca). Verificato end-to-end: manifest JSON valido + campi installabilità, sw.js/pwa.js sintassi OK, 70 pagine servite con manifest+pwa.js (0 gap), artefatti live 200 su prod (manifest/sw/offline/icone), AGID OK (0 pagine bloccano lo zoom, touch-target ≥44px, safe-area standalone, icone maskable).
Supervisore autonomo ticket (lavoro principale di oggi)
NIS2 = primo adopter LIVE dello standard AgileHub autonomous-supervisor-agent (hub_standards id=32). Un agente claude -p --model opus headless (chiave SSH effimera per ciclo) fa da supervisore end-to-end ai ticket NIS2 → NIS2 esce dal ticket-agent-cron generico.
- Cron LIVE:
/etc/cron.d/nis2-supervisor→17 */4 * * *(ogni 4h, minuto 17). Kill switch:touch /etc/agilehub/supervisor-nis2.disabled. Rollback:rm /etc/cron.d/nis2-supervisor. Audit:/var/log/nis2-supervisor-audit.jsonl. - Prompt: operativo
scripts/nis2-supervisor-acting-prompt.md(+ mie 2 aggiunte obbligatorie: cache-buster?v=su fix JS/CSS su tutti gli HTML referenti · git backup/commit chirurgico) + gate normativodocs/nis2-supervisor-prompt.md("fonti certe": no chiusura senza fonte citata, Allegato 3=importanti/4=essenziali, 24h/72h/1mese, DB SELECT-only container TLS, release edit+USR2 no maintenance, escala legale). - Validazione (2 run osservate, non-cron): run #1 ticket #366 (domanda normativa notifiche) → PASS gate (24h/72h/1 mese, Allegato 3/4 corretti, fonti Dir.2022/2555+D.Lgs.138/2024 Art.25+ACN,
[DA VERIFICARE]per comma non nel registro, 0 scritture DB/codice). run #2 ticket #367 (fix: rimossa icona FontAwesome mortafa-comment-altinhelp.js:770, residuo post-swap V2) → PASS release path, commitdcbce3d(37 file: 35 HTML?v=20260613→20260614+ help.js + version.json 1.14.1, 0 file backend/DB, fix live). - Doc archivio:
docs/INCOMING_FROM_AGILEHUB_2026_06_14_supervisor_adoption.md→ commit489a032(verificato su Gitea vials-remote). - Rotazione chiave Anthropic = RINVIATA (decisione VIGILE/AgileHub, risk-accepted: TTL breve + re-rotazione prevista + accesso protetto a monte + nessun segnale abuso). Trigger override monitorati (breach/IP inatteso/chiave in canale pubblico/scadenza senza re-rotazione). Verificato NIS2-side: la chiave esposta NON è in repo/git-history/
.env(solo vault cifratotier1__nis2-app__anthropic) → nessun trigger scattato. Il cron supervisore usa l'OAuth della flotta, non queste API key. Memoria:project_autonomous_supervisor.
Cron supervisore — confermato che parte DA SOLO (giri sera 14/6 → notte 15/6)
- 4 cicli
rc=0no-op verificati nell'audit JSONL: 16:17 / 20:17 / 00:17 (+ run osservate #366/#367). Ogni giro: pre-check OK (no kill switch, noagent-working.lock), chiave effimera installata→cycle_cleanup_done, 4 code tenant 7 vuote, nessuna release, heartbeat consegnato a cristiano.benassati@ (HTTP 201). HEAD non cambia ai giri (no-op). - Chi-ha-fatto-cosa: il commit
36513fa(PWA, 21:09) NON è del supervisore — nell'audit non c'è alcun ciclo alle 21:09 (giri solo a:17). Era lavoro interattivo mio. Dubbio chiuso. - Monitoraggio de-escalato (concordato con utente): il supervisore è indipendente (cron server + chiave effimera propria per ciclo + heartbeat al referente) → il mio check host-side è un livello bonus, non una dipendenza di sicurezza. Check all'occasione, niente poll ansioso.
- ⚠️ Lezione operativa — la chiave SSH host del MIO monitoraggio ruota ~14h: scaduta tra le 20:27 e le 00:16 (push + check host bloccati in quella finestra), AgileHub ne ha droppata una fresca in
/projects/nis2-agile/.ssh-temp/agilehub-host-temp(00:38) → ripristinato. NB: è cosa diversa dalla chiave effimera che il supervisore si genera da sé per ciclo (quella non dipende da me). - (TRPG, non NIS2) db-job 20: poller AgileHub — fuori scope NIS2.
Lavoro 13/6 (avatar/formazione + feedback) — dettaglio in memoria, qui sintesi
- Avatar di prodotto FORMAZIONE-first: 5 endpoint
/api/demo/*, dataset demo 996001 (RO) + 996002 (sandbox), guardapplyDemoGuard(read 200/write 403), manifest tournis2-tour-2026.json(10 step), frontend demo-mode (?demo=), tassonomia 10mod/28lez + banca esercizi. Collaudo E2E PASS sul subdomain. Memoria:project_demo_avatar. Handoff:docs/OUTGOING_TO_AGILEHUB_2026_06_13. - Regressioni Bootstrap Italia V2 fixate: FAB icone FA→SVG inline · conflitto
.modalBI (showModal invisibili) → override CSS · widget bug-reporter 401 → cablata X-API-Key · cache-buster?v=introdotto (JS/CSS erano senza versioning) · feedback consolidato sullo standard TRPG (rimosso auto-injectfeedback.js, tenuto solo bug-reporter). Memoria:project_bootstrap_italia_rollout. - Demo dataset "golden": 2 org coerenti (DataCore importante + MedClinic essenziale). Account reali preservati. Memoria:
project_demo_dataset.
Aperti / prossimi
- Osservazione supervisore 3 giorni →
hub_standards_adoption[id=32, nis2] = implemented ~21/6 (passiva, kill switch a portata). - Backlog/debito: cache headers server-side (evita i bump manuali
?v=) · token widget a basso privilegio (AgileHub-side). - ✅
docs/sql/039_integrity_keys.sqlora committato+pushato (inb80bf23, su Gitea).
2026-06-12 — ✅ AUDIT INTEGRITÀ DB + migrazione 039 (PK/UNIQUE/FK)
Task utente: "controlla se tutte le tabelle hanno le chiavi e le unique key corrette" → poi "procedi a sistema db" (dati demo, ok ad applicare).
Diagnosi (read-only, su DB reale prod)
- Sonda PHP via
information_schemalanciata dentronis2-app(l'unico che raggiunge il container DB172.21.0.4con la connessione TLS dell'app). Devenv NON raggiunge il DB → SSH host172.18.0.1(chiave.ssh-temp/host_key) +docker exec -i nis2-app sh -c 'set -a; eval "$(tr "\0" "\n" < /proc/1/environ | grep -E ^DB_)"; set +a; php /dev/stdin'(le creds DB sono vault-injected nel PID 1, NON nell'env didocker exec). Pattern riutilizzabile. ⚠️ La prima esecuzione conexport $(... | xargs -d)ha stampato l'intero env (segreti) in output — busybox non haxargs -d; usare la formaevalqui sopra che NON stampa. - Esito: 65 tabelle base, 65/65 con PRIMARY KEY, 65/65 InnoDB (nessuna anomalia bloccante).
TABLE_ROWSdi information_schema è approssimato (consulting_firms dava ~0 ma la firm 1 esiste) → i check orfani/duplicati usanoCOUNT()reale.
Sistemato (migrazione 039, applicata su prod — 18 vincoli, 0 errori)
Runner idempotente PHP con pre-check duplicati/orfani/tipi. File: docs/sql/039_integrity_keys.sql (NON committato ancora).
- 🗑️ Drop indice UNIQUE ridondante
invites.token_hash(duplicavauq_invites_token). - 🔑 3 UNIQUE su chiavi naturali (0 duplicati reali):
api_keys.key_hash,refresh_tokens.token,consulting_firms.vat_number. - 🔗 14 FK mancanti stesso-DB:
policy_attestations(×3),policy_versions(×2),org_acn_requirement_status(×2),supplier_questionnaires(×2),org_connectors,control_evidence_auto,firm_org_assignments.consulting_firm_id,kri(×2).ON DELETE: organization_id/padre→CASCADE, link nullable (kri.linked_risk_id)→SET NULL. DB ora a 109 FK totali.
Differiti (NON applicati — richiedono decisione)
supplier_categories.organization_id=0(10 righe) = sentinel "categoria globale di sistema" (seed mig.033) → FK sarebbe scorretta per design. Lasciato.firm_org_assignments.organization_id→ org 126/127/128/129 inesistenti (dogfooding "Agile" firm 1, ancheassigned_to=0). Per la FK serve prima: seedare le org 126-129 oppure DELETE delle 9 righe stale, poiADD CONSTRAINT fk_firm_org_assignments_organization_id(DDL pronto in fondo al file 039).
Aperti / prossimi passi
- Commit + push di
docs/sql/039_integrity_keys.sqlsu Gitea (atteso ok utente; push via host/VIGILE — token devenv bloccato). - Decidere caso
firm_org_assignmentsorfano (seed vs delete) → poi chiudere l'ultima FK.
2026-06-11 — ✅ CUTOVER TLS-DB FATTO: NIS2 su container nis2-db (TCP+TLS), anomalia socket chiusa
Dopo 3 outage notturni (TLS-su-socket), risolto definitivo con lo standard "come TRPG":
- VIGILE ha migrato host→container
nis2-db(copia esatta 60 tab/19 utenti/max_id 392; host intatto, backup/root/backups/nis2-migration-20260611/). - Io:
.envDB_HOST=172.21.0.4(IP del container — fpm NON risolvedb) + reload fpm (docker exec nis2-app kill -USR2 1) → TCP verde (login reale 401, container, 19 utenti). Poitouch application/config/.db_ssl_on(+ CA container già giusta) + reload →Ssl_version=TLSv1.3dal worker reale, login Fattori 200, ARIA RAG ok → TLS verde. - VIGILE:
ALTER USER 'nis2_user'@'%' REQUIRE SSLsul container → post-check verde: plain no-SSL rifiutato (1045), app 200/ok. - Guard anti-outage in
Database::sslOptions()(commit4548cc6): salta SSL su connessione socket → riaccendere il flag su socket non può più causare outage. - Tecnica usata senza SSH continuo: edit file bind-mounted (
.envposseduto da uid 1000,application/config/,public/) + diag serviti dapublic/via HTTPS + reload fpm via SSH host. Regola d'oro confermata: verificare SEMPRE dal worker fpm reale (login), MAI da CLI/docker exec/api-status (CLI dava falsi positivi: DB_HOST e source-IP divergono). - File d'ambiente (
.env,.db_ssl_on,db-ca.pem) sono gitignored (corretto). Memoriaproject_db_topologyaggiornata (ora: container via TCP+TLS). - ⏭️ Residuo VIGILE: decommission host (
DROP nis2_user@localhost) quando confermata stabilità → NIS2 self-contained come TRPG.
2026-06-10 — TLS-DB pre-equip (VIGILE) + SCOPERTA split DB host/container
Task da CLAUDE.md (cima, "VIGILE 2026-06-10"): pre-equip PHP-PDO/CA per TLS-in-transito DB, gated default-OFF, senza enforce (l'
ALTER USERè di VIGILE). Eseguito con precisione chirurgica + 2 agenti background di review.
Fatto (commit 4d89b1e)
application/config/database.php→Database::sslOptions(): TLS gated (envDB_SSL=trueo flag-fileapplication/config/.db_ssl_on), DEFAULT OFF.PDO::MYSQL_ATTR_SSL_CA+VERIFY_SERVER_CERT=false. Fail-safe: CA assente → resta in chiaro +error_log(un VERIFY=false senza CA in mysqlnd connette IN CHIARO silenziosamente — doc §11). Merge con+(preserva chiavi-intere PDO, §14).- Verificato locale (no DB):
php -l+ reflection test → OFF ⇒base + [] === base(inerzia provata, zero regressione); ON+CA-mancante ⇒[]; ON+CA ⇒ chiavi1009/1014. - 2 agenti background: (a) censimento connessioni → runtime
application/coperto 100% daDatabase::getInstance()(0 raw PDO); 9new PDOraw solo in NON-prod (simulate-*.phpcome root → ininfluenti;scripts/import-feedback-to-nexus.php:112/114cross-DB epublic/test-runner.php:529da gestire prima dell'enforce SE usano nis2_user). (b) review adversariale → SICURO default-OFF, nessun bug. .gitignore:application/config/.db_ssl_on+db-ca.pem. Runbook completo:docs/TLS_DB_PREEQUIP_NIS2.md.
🔴 SCOPERTA CRITICA: split DB host vs container (confermata da VIGILE)
- La recon (
docker exec nis2-app php -r 'Database::getInstance...') ha rivelato: l'app nis2 usa il DB CONTAINER (DB_HOST=db → 172.21.0.4, serverfd63ec13e889, MySQL 8.0.45), NON il MySQL host (8.0.46). Questo CORREGGE la memoriaproject_db_topology(diceva "host").Ssl_version=[](chiaro),nis2_user@%. - VIGILE ha confermato lo split + fixato: il cron
sso-password-syncaggiornava le copie host MORTE dinis2_agile_db(+ lg231/wms) mentre le app usano i container → password SSO non arrivavano. Fixato (sync sul container reale viadocker exec). taxai resta su host (la sua app usa davvero l'host). trpg/sustainai intatti. - Conseguenza sul mio lavoro 2026-05-29: migrazioni 020/021/022 erano state applicate via
mysql -h localhost= copia host morta → mancanti nel container → v1.7.0 NON funzionava in prod. VIGILE le ha ri-applicate al container (backup+idempotenti) → verificatoassets.relevance_score/incidents.nis2_incident_type/incident_pir✅. Schema v1.7.0 ora corretto in prod. - ⚠️ Per il TLS: CA +
ALTER USER nis2_user@% REQUIRE SSLvanno sul CONTAINERdb(non host). Comandi corretti nel runbook.
Aperti (in mano a VIGILE — DB = solo Agile)
- Benassati/org129: assente nel container (host id=4, SSO #232); membership org129=0 sia host sia container. Stato voluto comunicato: super_admin (NO declassare) + org_admin membro org 129 nel CONTAINER, SSO-linked. NB
add-agile-user.phpcrea i nuovi comeorg_admin→ NON usarlo as-is per lui; probabile gap provisioning SSO nel container. VIGILE inserisce da contestoDB_HOST=db. - Riconciliazione migrazioni 023→036: applicate (sessioni 05-30/05-31) via
mysql -h localhost= path host → da verificare/ri-applicare sul container come 020-022. Sentinelle per-migrazione fornite a VIGILE (tabelladocs/TLS_DB_PREEQUIP_NIS2.md+ chat). Offerto script SQL unico di reconciliation (information_schema) — da generare se richiesto. - Enforce TLS: di VIGILE, con 3-sì, dopo CA distribuito sul container + verifica sul path reale (
Ssl_version=TLSv1.3, lezione §16 "no test sintetici").
Accesso
Chiave SSH effimera in .ssh-temp/ (rigenerata dall'utente, validità ~3h). Devenv NON raggiunge il DB prod direttamente: solo via SSH host (root@172.18.0.1 o 135.181.149.254). Push Gitea bloccato sul token (git-login interattivo) — commit locali, push a cura host/VIGILE.
🔴🔴 AGGIORNAMENTO SERA — la "scoperta container" era SBAGLIATA; verità: fpm→HOST via SOCKET
Le conclusioni "container DB" qui sopra sono superate. Verificato col path fpm reale (diag PDO servito da php-fpm, NON
docker execche è CLI):
- L'app HTTP (php-fpm/www-data) usa il MySQL HOST
dfm-dev(8.0.46) via UNIX SOCKET (localhost, env non propagato ai worker). Lo schema v1.7.0 è presente sul host (le migrazioni 020/021/022 del 29/5 viamysql -h localhosterano sul DB GIUSTO). Ildocker exec nis2-app php(DB_HOST=db→container 8.0.45) è solo il path CLI, secondario. Memoriaproject_db_topologyri-corretta (fpm→host socket; CLI→container). - OUTAGE causato + risolto: VIGILE ha attivato CA+flag+
REQUIRE SSL(sul container) → ma TLS su UNIX socket non si applica e ROMPE ([2002] Cannot connect to MySQL using SSL) → app giù (login 500). Fix:rm application/config/.db_ssl_on+kill -USR2 1→ ripristino. VIGILE poi: NIS2 🔵 escluso dall'enforce (caso socket), container revertito a baseline, app verde. - Tentativo switch socket→TCP (per allinearlo agli altri): VIGILE ha aperto un path TCP via vault-net (
172.30.0.1, host MySQL già in ascolto lì) + grantnis2_user@'172.30.%'. MA il path fpm reale FALLISCE: prova affiancata 3+3 → CLI (root)getsockname=172.30.0.9→nis2_user@172.30.0.9OK; fpm (www-data)getsockname=**172.30.0.1**→ MySQL vede 135.181.149.254 (IP pubblico) → 1045. Causa probabile: policy-routing/NAT per-uid (www-data instradato via gateway). Il collaudo "OK" di VIGILE era CLI = falso positivo. → switch NON eseguito (sarebbe outage). Palla a VIGILE: grant@'135.181.149.254'(quick) o fix routing per-uid; poi ri-probo da fpm prima di switchare. - Stato finale: app verde (socket clear), pre-equip TLS inerte (flag OFF),
db-ca.pempresente ma inerte. Patch codice committato (4d89b1e), corretto e pronto; non attivabile finché l'app è su socket / il TCP fpm non è grantabile. - ⚠️ Lezione ricorrente della sera: validare SEMPRE sul path fpm reale (diag servito da php-fpm: login/business), mai
docker exec/CLI né/health— CLI e fpm divergono (DB_HOST, source IP, opcache).
2026-06-01 — Modulo Gap Analysis ACN + review + landing (prod v1.13.0, ahead 0)
⚠️ EMAIL ancora DISABILITATE (
EMAIL_SENDING_ENABLED=false, solo dati demo). Vedi sezione 2026-05-31 + memoriaproject_email_killswitch.
Origine: finding revisore — la Gap Analysis Art.21 (10 lettere) NON è l'autovalutazione ACN reale (misure/requisiti puntuali). Verifica su fonti ACN in docs/ANALISI_GAP_ANALYSIS_ACN.md (revisore aveva ragione).
NUOVO modulo Gap Analysis ACN (Determinazione 164179/2025, assessment 2° livello):
- IMPORTANTI 37 misure/87 requisiti, ESSENZIALI 43/116. Filtra per
organizations.entity_type. - Dataset canonico
application/data/acn_measures.json(testi INTEGRALI da Allegati 1/2, flag per-requisito importante/essenziale, validato 0 discrepanze). AcnAssessmentController.php(catalog/list/create/get/requirements/respond/complete/report/aiAnalyze), scoring per funzione FW (GV/ID/PR/DE/RS/RC), anti-IDOR. Migrazione 036 (acn_assessments+acn_assessment_responses) APPLICATA su host.public/acn-gap.html+ voce sidebar + metodiapi.acn*. Gate: guida cap-5b, help 'acn', i18n IT/EN, version 1.13.0. E2E verificato in prod (catalog 87, scoring esatto).
Review avversariale 4 agenti → 5 bug fixati (commit d6924a3):
- CRITICO aiAnalyze trattava return array di askWithRag come stringa → ora estrae 'answer'.
- ALTO:
acn_requirements.json(sorgente ingest Qdrant) aveva 188/203 testi TRONCATI → rigenerato integrale + ri-ingest (chunk 458-1052 char). NB: container nis2-app monta SOLO /application e /public → per ingest servedocker cpscript+json in /tmp + symlink /application. - MEDIO catalog() entity_level=null → 422. MEDIO guida cap-5 "figure chiave organigramma" → "personale autorizzato + sysadmin". BASSI: openAcn try/catch, badge IT/EN, overall_score null se tutti N/A.
Landing/presentation (commerciale):
public/index.htmlaggiornata alle reali funzionalità (8→12 moduli: Gap Analysis ACN in evidenza, Gap Analysis Art.21, Portale Fornitori, AI Consulente RAG, KB multi-livello), hero stats (116 req ACN, 12 moduli, RAG), meta description, trust-items.- Rimossi TUTTI i riferimenti a costi/gratuità (richiesta utente "sembra gratis"): index CTA ("nessuna carta di credito"→"accesso controllato"), presentation.html slide 9 ("Modello commerciale/abbonamento annuale"→"Edizioni") + contatti ("Pricing"→"Attivazione su richiesta").
- FIX bug layout consenso GDPR nel form:
.form-group input{width:100%}gonfiava il checkbox → testo incolonnato verticale + tutto rosso. Fix classe.consent-label(checkbox 16px, span flex, colore normale). Verificato con render headless Chrome. Causa vera trovata solo al 3° tentativo.
Memorie aggiornate: project_acn_gap_analysis (nuova), project_email_killswitch (precedente). docs/ANALISI_GAP_ANALYSIS_ACN.md committato.
2026-05-31 (sera) — Fasi 2-4 questionari fornitori + corpus ACN + KILL-SWITCH EMAIL (prod, ahead 0)
⚠️ EMAIL DISABILITATE — ambiente con SOLI dati demo.
EMAIL_SENDING_ENABLED=false(fail-safe). NESSUNA mail parte. Riattivare SOLO al go-live reale e SOLO dopo aver rimosso i dati demo. Vedi sotto + memoriaproject_email_killswitch.
Kill-switch email (commit 397185e, ultimo lavoro della sessione)
- Guard
EMAIL_SENDING_ENABLEDinconfig.php(default false SEMPRE, anche con APP_ENV=production che qui è di fatto demo) all'inizio diEmailService::sendViaRelay()EsendViaTemplate()— gli unici 2 punti che fanno HTTP al relay → coprono TUTTI i canali (incidenti, training, inviti, reminder, welcome, password reset, feedback, OTP portale, cron Fase 2). - Verificato a runtime nel container:
EMAIL_SENDING_ENABLED=false,send()→false,sendViaTemplate()→false (nessuna chiamata rete). - Contesto scoperto:
INTERNAL_EMAIL_KEY=nexus-internal-2026valorizzata +APP_ENV=productionnel.envhost → senza switch le mail SAREBBERO partite davvero. - Riattivazione go-live:
EMAIL_SENDING_ENABLED=truevia env/vault +kill -USR2 1.
Fix collaterale (commit 006f863)
EmailService::sendViaTemplate()era MANCANTE (Edit silenziosamente fallito inde09af6):requestOtp()chiamava un metodo inesistente → OTP mai inviato (errore inghiottito dal try/catch). Aggiunto davvero + verificatomethod_existsa runtime. Payload usadata(campo canonico relay AgileHub) + aliasvars.
Modulo questionari fornitori al 100%. Tutto committato e pushato.
Fase 2 — campagne + automazioni: backend campagne+cruscotto (7baa596) già pronto; scripts/supplier-questionnaire-runner.php + .sh (261fc4c) — 3 fasi idempotenti (REMINDER su reminder_offsets, OVERDUE due_at<NOW→expired, RICORRENZA clona con token sq_), dry-run host OK. AZIONE UTENTE: registrare crontab 0 6 * * * via agile-services.
Fase 3 — portale OTP/magic-link: SupplierPortalController.php (de09af6) auth SEPARATA (SUPPLIER_JWT_SECRET, aud=supplier-portal, supplier_sessions revocabili; OTP 8 cifre lockout persistente; sendViaTemplate fuori da email_log; request-otp opaco; no-IDOR). public/supplier-portal.html (78dcb41) — E2E verificato in prod (verify-otp→token→/me→getQuestionnaire 26 domande→PATCH autosave), poi cleanup. Config: SUPPLIER_JWT_SECRET + PATCH in CORS. Migrazioni 034+035 applicate.
Fase 4: tutti i tipi domanda nel portale + scoring per-vulnerabilità (Art.21.3) in submitQuestionnaire.
Corpus AI: scripts/ingest-acn-requirements.php (6365d5d) — 203 requisiti ACN in Qdrant nis2_kb (entity_type=requisito_acn), verificato count=203.
Gate (2dced3f): guida cap-9 + help.js + version 1.12.0.
Residue (non bloccanti dev): crontab; go-live (chiave mktg, APP_ENV).
2026-05-31 — Modulo Questionari Fornitori (Fase 0+1) + doppia review 22 agenti (HEAD 43a7180, ahead 0, prod v1.11.0)
Ambiente: SVILUPPO. Le 2 azioni di sicurezza (ruotare chiave API mktg nel DB, impostare APP_ENV=production) sono RIMANDATE — non servono ora, da rifare prima di un go-live reale (decisione utente).
COSA È STATO FATTO
- Design modulo questionari fornitori + portale OTP + AI consulente: 5 review + 4 deliverable (template GV.SC, DDL, UX mini-spec, AI consulente). Doc:
docs/DESIGN_MODULO_QUESTIONARI_FORNITORI.md+docs/supplier-portal/. Commitaa2db4c. - Fase 0 (email relay):
EmailService::send()ora instrada via relay AgileHub/api/emails/send-raw(X-Internal-Key, env multi-source, maskEmail). Fixa bug prod mail(). Smoke E2E = SENT. Commit5457611. - Fase 1 (modulo questionari configurabile):
- Migrazioni 032+033 APPLICATE su host (mysql -h localhost): 6 tabelle nuove + 10 categorie preset (org 0) + suppliers.category_id/external_ref/source. 034/035 NON applicate (Fase 2/3).
- Backend
SupplyChainController: CRUD categorie/template/domande + import CSV/API (bulkUpsert per external_ref). Route in index.php. - Seed template "NIS2 base" = 26 domande GV.SC su org 129 (Agile Technology), mappate ad Allegato 2 ACN con nis2_ref + disclaimer interpretazioni.
- UI
supply-chain.html: dropdown categoria nel form + modali Categorie (CRUD) e Template (read-only domande con badge nis2_ref) + import CSV con errors[]. i18n sp.* IT/EN.
- 2 REVIEW MULTI-AGENTE (12 + 10 = 22 agenti) su tutta l'app/help/i18n/guida/AI. Bug reali fixati:
- CRITICI: KB token-key errata + init mancante (
8a55194); register() senza jti → utente buttato fuori + revoca sessione singola 404 (c134a2d); incidents.html SyntaxError apostrofi (a92e27c, sessione precedente). - MEDI: dashboard gauge 0% (fallback score), risks backToList 4-viste + loadFair data.items (
2037cec); selettore modale #app-modal→#modal-overlay (4d9153b); deleteAsset this.delete→del (d103e9d). - NORMATIVA: Allegato 3=IMPORTANTI / 4=ESSENZIALI corretto in AIService+help+IncidentController (era invertito); relazione finale = notifica+1mese (Art.23.4.d) non rilevazione+30gg; disclaimer non-parere-legale in guida+AI (authoritativeSourcesBlock regole 4-5); citazioni GV.SC con [Interpretazione] dove serve.
- AI: crossOrgAnalysis +fonti certe, suggestRisks anonimizzato (employeeRange), EmbedService/VectorService dim 512.
- SECURITY F1: chiave API mktg
nis2_mktg_8c8bd38e...(scope admin:licenses) era ESPOSTA in mktg-api-doc.html + integrazioniext.html → redatta a placeholder (e269fb1). DA RUOTARE nel DB (resta in git history) prima del go-live.
- CRITICI: KB token-key errata + init mancante (
- Rifinitura help: aggiunto help contestuale ai 4 moduli che ne erano privi (whistleblowing, normative, kb, companies) → ora tutti i 16 moduli hanno il pulsante "?". Commit
c1d3328+3e5b75b+43a7180.
ESITO REVIEW: app SOLIDA — 0 SQLi/IDOR/XSS cross-tenant, JWT+refresh atomico, DB integro (0 orfani, 26 domande org 129, 10 categorie), 0 citazioni normative inventate.
TODO RESIDUI (non bloccanti, vedi memoria project_supplier_portal):
- i18n EN: dizionario 278 chiavi ESISTE ma non cablato nei markup → app di fatto solo-IT (decisione utente: lasciare, IT è lingua primaria).
- Collation drift LOW su tabelle PRE-esistenti
invites+incident_pir(utf8mb4_0900_ai_ci) — non tocca il modulo fornitori, NON ancora sistemato (richiede ALTER, chiedere conferma). - Cut-over
SupplyChainControllerdasupplier_questionnaires(027) aquestionnaire_campaignsda fare in lockstep con migrazione 035 (Fase 2/3). - Per-account login lockout (oggi solo per-IP).
- AI/KB grounding: D.Lgs.138/2024 assente dalla KB (file=null), 203 requisiti ACN solo in DB non in Qdrant, Determina 164179 sotto-estratta (9 chunk) — vedi
docs/supplier-portal/AI_CONSULENTE_NORMATIVO.mdper il piano. - 2 azioni go-live (rimandate, ambiente sviluppo): ruotare chiave API mktg; APP_ENV=production su Hetzner.
2026-05-30 (sera) — Guida allineata + email invito
- guida.html allineata alle 10 nuove funzionalità (commit
5e2534eFAIR/KRI/benchmark +397d181ingestion SIEM, attestation+versioning policy, self-assessment fornitori, import CMDB, monitoraggio continuo, connettori per-azienda). Stile coerente "in parole semplici/esempio/norma". Àncorecap-1..cap-15intatte (help.js continua a linkare i capitoli giusti via_guideAnchor). HTML bilanciato. HEAD397d181, ahead 0. - Email invito guida inviata (HTTP 202, queued) a m.tagliavini + s.fattori + presidenza @agile.software, firma "Cristiano Benassati", reply_to cristiano.benassati@gmail.com.
- ⚠️ Lezione email: l'invio NON funziona con
EmailService::send(mail() nativa → sendmail nel container rifiuta,127.0.0.1:25connection refused) né conAuthorization: Bearer. Il canale CORRETTO è il relay AgileHub:POST https://agilehub.agile.software/api/emails/sendcon headerX-Internal-Key: <INTERNAL_EMAIL_KEY>(in.env, keylen 20), body JSON{to,subject,html,product:'nis2',reply_to,priority}. Doc canonico:docs/STANDARD_EMAIL_RELAY.md. Postfix diretto è bloccato per tutti i prodotti.
- ⚠️ Lezione email: l'invio NON funziona con
2026-05-30 — Risoluzione bug residui post-review (HEAD 8d1f0f9, ahead 0)
Dopo i fix dei 🔴 (vedi sotto), risolti anche i ⚠️ di correttezza-dati segnalati dai 5 agenti:
a3f8211Bug #1+#4 supply chain:submitPublicQuestionnaireora coerente conassessSupplier(risk_score=compliance alto=buono,security_requirements_metsoglia 70, NON sovrascrivecriticality) + completamento ATOMICO (UPDATE ... WHERE status='sent'+rowCount()==0→409). E2E: 201 poi 409.9c64ae7Bug #2 computeFair: validazione range (TEF≤1M, LM≤1000mld, no negativi/NaN, vuln≤1) → no overflow DECIMAL(16,2). E2E: valido 200, overflow/vuln>1/negativo→422.4e8a90cBug #3 sectorBenchmark: dedup pool con subquery correlata (ultimo completato, tie-break id, LIMIT 1) → una riga per org anche con timestamp identici. E2E: org con 2 assessment stesso TS → peers=4 (non 5). NB: primo tentativo (Edit fallito su ancora) committato vuoto, fix reale in 4e8a90c.- ⚠️ Falso allarme mio: ho erroneamente segnalato "prompt-injection" negli output tool — verificato che NON esisteva (grep repo vuoto, nessun hook in settings). Ritrattato. Nessuna esfiltrazione richiesta/eseguita.
- Tutti gli 8 controller lint-clean. Nota workflow ricorrente: gli
Editfalliscono se l'ancora non combacia → SEMPRE Read del file reale prima, e verifica esito (grep/curl) prima del commit.
SESSIONE 2026-05-30 (gap competitivi P2/P3 + UI) — riepilogo finale
Stato git: origin/main = 172d927, ahead 0. Tutto su Gitea.
Completato e VERIFICATO E2E in produzione
- 4 feature P1/P2 backend + UI (sessione mattina): ingestion incidenti, evidence automation, asset import, FAIR/KRI. UI in risks/reports/assets + help/i18n/OpenAPI/scorecard EVIX.
- P2 Benchmark settoriale anonimizzato:
GET /dashboard/sectorBenchmark(k-anonymity ≥3 org) + UI pannello dashboard. 200 OK. - P3 Supply chain self-assessment: migrazione 027,
SupplyChainController(sendQuestionnaire JWT + publicQuestionnaire/submitPublicQuestionnaire NO-auth token + questionnaireStatus) +public/supplier-assessment.html. E2E: send 201→publicGET 200 (8 domande)→submit 201 (score 61)→re-submit 409→suppliers.risk_score aggiornato. - P3 Policy attestation+versioning: migrazione 028,
PolicyController(attest/attestations/versions/diff/pendingAttestations + snapshot in approve). E2E: approve→attest(coverage)→bump v2.0→diff(+2/-1)→pending ricompare.
⚠️ LEZIONE RICORRENTE (grave, ripetuta più volte oggi)
Le Edit falliscono silenziosamente quando l'ancora non combacia col file reale, e ho committato senza ri-verificare via HTTP → 3 commit P2/P3 erano gusci vuoti (migrazione+HTML ma metodi/route mancanti → 501/404). Corretti con commit successivi (31b8a45, 172d927) DOPO smoke test. Regola d'oro: dopo ogni feature, curl l'endpoint reale PRIMA del commit; non fidarsi del "Edit success". Inoltre Edit richiede Read del file nella sessione corrente.
🔌 Connettori per-azienda nella card (RICHIESTA UTENTE, in sospeso — decisione necessaria)
Utente vuole: "le credenziali si configurano nella card per ogni azienda cliente" → card in companies.html (consulente), storage segreti scelto vault-steward.
Finding (verificato sull'host): vault-steward (server.js 2123 righe) e nis2-vault-proxy esistono; nis2-app ha VAULT_APP_TOKEN scoped tier1__nis2-app__* e fetcha al boot (READ). NON risulta un'API runtime che l'app possa usare per SCRIVERE nuovi segreti: il populate è operazione ADMIN via docker exec vault-steward node scripts/vault-repopulate.js + register-app (CLI), non esposta al token applicativo. VAULT_STEWARD.md non montato nel devenv.
→ DECISO e IMPLEMENTATO (opzione A — commit 6c8f8e2): config in DB, secret nel vault via CLI.
- Migrazione
029: tabellaorg_connectors(organization_id, connector_type ENUM 8 tipi, display_name, enabled, config JSON NON-segreto, vault_key_alias, secret_status, last_status). NESSUN segreto nel DB. OrganizationController:listConnectors/saveConnector/deleteConnector+connectorOrgGuard(org_admin/compliance_manager della propria org, o firm che la gestisce, o super_admin). Difesa secret-strip: client_secret/api_key/password/private_key/token inviati vengono rimossi prima del save (verificato E2E: NON entrano nel DB).saveConnectorritornacli_hintcol comandovault-cli.js migrate <alias> <key> <value>.- Route
organizations/{id}/connectorsGET/PUT/DELETE (type nel body). - UI: pannello "Connettori" nella card di
companies.html(8 tipi, tenant/client id, toggle attivo, badge stato segreto, modal). Servito 200, JS valido. - Vault write-path confermato assente per l'app:
server.js(615 righe) espone soloGET /v1/credentials/*+ tier2 unlock/lock; scrivere segreti è solo CLI admin (vault-cli.js migrate). Doc:/opt/devenv/VAULT_STEWARD.md. - E2E in prod: list vuota 200 → save m365 201 (secret strippato) → DB pulito → delete OK.
Stato git fine sessione: origin/main = 6c8f8e2, ahead 0. Tutto su Gitea.
REVIEW MULTI-AGENTE (5 agenti) + FIX — fine sessione 2026-05-30
Lanciati 5 agenti read-only di review (P1, P2, P3, connettori, frontend). Esito: nessuna vuln critica nelle feature, ma trovati problemi reali. Tutti corretti, testati E2E, pushati. Stato finale: origin/main = dac41f8, ahead 0.
- 🔴
8e3a2c1risks.html: apostrofi non escapati in LIKELIHOOD_DETAILS (bug PRE-ESISTENTE dal 2026-02-20) rompevano l'INTERO script della pagina Rischi (FAIR/KRI inclusi). Fix + verificato LIVE. - 🔴
9f4d2a1Connettori (SEC): (1)connectorOrgGuardusavausers.roleglobale invece del ruolo per-org → feature ROTTA per utenti reali; ora ancorata al route{id}+user_organizations.role. (2) secret-strip denylist→allowlist ricorsiva. E2E: globale=employee+per-org=org_admin→200; non-membro→403; 11 varianti segreti→DB solo {account_id,region}. - 🟠
c7e1f04Policy: migrazione 030 UNIQUE(policy_id,version) + approve() ON DUPLICATE KEY UPDATE (no snapshot duplicati); diff set-based→LCS posizionale. E2E ok. - 🟠
b2e9c33P1 ingestion: retry su collisione incident_code + race external_ref→200 dedup (no più 500=alert perso); try/catch su CCM services. - 🟡
dac41f8P2:relevance_criteriauniformato (score() salva criteri piatti come bulkUpsert). - ✅ Falso allarme agente P1:
getNistCsfMapping/getIsoMappingESISTONO (verificato). - Migrazioni totali sessione: 023-030. ⚠️ Le 027/028/029 NON erano state pushate come gusci? No: tutte applicate in prod + codice completo dopo i fix.
SESSIONE 2026-05-30 mattina (~07:50→09:15 CEST) — Chiusura gap competitivi Evix (backend)
Obiettivo utente: "NIS2 punto di riferimento" → implementare DAVVERO le carenze vs concorrenza (Vanta/Drata/GRC) dall'analisi docs/EVIX_ANALISI_CONCORRENZA.html. 4 feature backend complete, testate E2E in prod, committate e pushate.
Feature implementate (tutte su Services API + scope dedicati)
- P1 Ingestion incidenti SIEM/SOC/EDR (commit
2190999):POST /api/services/incidents-ingest(scopeingest:incidents). Crea incidente Art.23 da alert esterno, dedup suexternal_ref,mapSeverity(CVSS/P1-P5/stringhe→enum), classificazione AI (IS-1..4), deadline 24h/72h/30g, webhook. Migrazione 023 (incidents += source/source_system/external_ref + UNIQUE dedup). - P1 Evidence Automation + Continuous Control Monitoring (commit
307993f):POST /api/services/evidence-ingest(scopeingest:evidence, batch ≤200) +GET /api/services/controls-monitoring(scope read:compliance). Stato semaforo healthy/warning/stale/failing per freschezza. Migrazione 024 (tabellacontrol_evidence_auto+ compliance_controls += monitoring_status/last_checked_at/freshness_days). - P2 Asset import CMDB/cloud (commit
4924075):POST /api/assets/import(JWT) +POST /api/services/assets-ingest(scopeingest:assets).AssetScoringService::inferCriteria(euristica 6 criteri GV.OC-04 da campi CMDB) → scoring automatico. Upsert dedup su external_ref, max 1000/batch. Migrazione 025 (assets += external_ref/discovery_source + UNIQUE). - P2 Risk quantitativo FAIR + KRI (commit
a3f2e91):FairServiceMonte Carlo (PERT su TEF/Loss Magnitude, ALE EUR P10/P50/P90, deterministico).POST /risks/{id}/fair,GET /risks/fairRegister(portfolio ALE), KRI CRUDGET/POST /risks/kri+PUT /risks/kri/{id}con semaforo green/amber/red. Migrazione 026 (risks += params FAIR/ALE; tabellakri).
UI area provider (commit 094d453)
public/integrazioniext.html: sezione "Catalogo Connettori — Evidence Automation" con 8 card "In roadmap" (M365/Google/AWS/Azure/IdP/EDR/SIEM/Ticketing) + badge .badge-roadmap.
API key create (org 129 Agile, prod — salvate dall'utente)
SIEM nis2_siem_70246bbf..., lg231 nis2_lg231_2b6b4c09..., SustainAI nis2_sustai_30fb3796..., AllRisk nis2_allrsk_ecb9ea92..., Evidence nis2_evid_4cc44366..., CMDB nis2_cmdb_ebbc0dc8.... (key_prefix = varchar(12)! created_by NOT NULL → uso 330.)
⚠️ LEZIONI CRITICHE (cambiano il workflow)
- 🔴 OPcache
validate_timestamps=Off: le modifiche PHP NON sono live finché non si fadocker exec nis2-app sh -c 'kill -USR2 1'(reload graceful). Il bind-mount aggiorna il file ma php-fpm serve il bytecode in cache. - 🔴 Modifiche non committate a RISCHIO: durante la sessione un commit esterno (
d5d83bb, cron agent/operatore) ha fatto checkout e revertato la mia route in index.php non committata. → committare+pushare appena una feature è verde. - ✅ Push su Gitea: fatto via SSH dall'host (
git pushcon helper vault), il container devenv non ha il token.
✅ Completamento UI + contorni (commit 8a2f4d1, pushato)
- UI frontend delle 4 feature (ora usabili dagli utenti, non solo via API):
risks.html: viste "Quantitativo (FAIR)" (form + istogramma ALE + registro portafoglio) e "KRI" (dashboard semafori + CRUD)reports.html: tab "Monitoraggio Continuo" (semafori freschezza controlli + copertura)assets.html: bottone "Importa" CSV/CMDB (parsing client + scoring auto GV.OC-04)api.js: metodi computeFair/getFairRegister/listKri/createKri/updateKri/importAssets/getControlsMonitoring
- help.js: guide FAIR+KRI / import CMDB / monitoraggio continuo. i18n.js: chiavi IT/EN.
- OpenAPI
/api/services/openapi: aggiunti incidents-ingest/evidence-ingest/assets-ingest/controls-monitoring + ApiKeyAuth. - AuditController::controlsMonitoring (versione JWT per UI) + route
audit/controlsMonitoring. - EVIX scorecard: gap P1/P2 marcati chiusi (backend), roadmap P1/P2 con voci ✅ FATTO.
- Verificato in prod: OpenAPI espone i 4 endpoint, pagine 200, import JWT 201, monitoring JWT 200.
✅ Gap P2/P3 rimanenti — IMPLEMENTATI (sessione 2026-05-30, pomeriggio)
- P2 Benchmark settoriale anonimizzato (commit
a673033):GET /dashboard/sectorBenchmark— confronta score org vs aggregati anonimi del settore (avg/median/p25/p75/percentile/posizione), k-anonymity ≥3 org. UI: pannello dashboard con barra distribuzione. Differenziatore rete multi-tenant. Testato E2E (peers=4, avg/median esatti). - P3 Supply chain self-assessment (commit
8f4e8e9): migrazione027(tabellasupplier_questionnaires),SupplyChainController::sendQuestionnaire(JWT, link token 30gg) +publicQuestionnaire/submitPublicQuestionnaire(NO auth, token) +questionnaireStatus. 8 domande Art.21.2.d pesate → score →suppliers.risk_score. Pagina pubblicapublic/supplier-assessment.html(standalone). Pulito route map (rimossi duplicati +PueTgarbage + metodi inesistenti). Testato E2E (send→public GET→submit→dedup 409→bad token 404). - P3 Policy attestation + versioning (commit
75de1c2): migrazione028(policy_versions+policy_attestations),approve()crea snapshot versione,attest/attestations(copertura %)/pendingAttestations/versions/diff?from&to. Version-aware: bump versione → richiede ri-attestazione (verificato E2E: diff added/removed corretto, pending ricompare dopo v2.0).
⚠️ Trovato file PRE-CORROTTO (non toccato)
public/supply-chain.html è corrotto in HEAD da prima (committato 2025-09-29): righe garbage the ▐ }, const file = list.clea; // BUG, funzioni duplicate. NON è di questa sessione. La UI admin per inviare questionari fornitori va aggiunta dopo aver riparato questa pagina (task separato). Il backend + portale pubblico P3 supply chain funzionano comunque via API.
❌ Restano (prossimi passi reali)
- Riparare
public/supply-chain.html(pre-corrotto) + aggiungere UI admin "Invia questionario" / stato attestation policy / lista versioni+diff. - Connettori per-vendor live (OAuth M365/Google/AWS/Azure/IdP/EDR): le 8 card sono "In roadmap"; servono client + credenziali/app-registration lato cliente.
- Auto-discovery attiva asset (agent/scan) — l'import bulk e gia disponibile.
- Gap P2 Reporting/benchmark settoriale e P3 Supply chain/policy non ancora affrontati.
- NB workflow: OPcache
validate_timestamps=Off→ dopo ogni modifica PHP servedocker exec nis2-app sh -c 'kill -USR2 1'. Le modifiche a HTML/JS sono servite da nginx senza reload.
SESSIONE POMERIGGIO — 2026-05-29 (~15:30→17:10 CEST)
0. Perché "tutti i prompt sono morti improvvisamente"
Il container nis2-agile-devenv si è RIAVVIATO alle 15:25:55 CEST. Prova: uptime (up 4 min) + supervisord.log (supervisord ripartito da pid 98 alle 15:27, nessun crash/respawn interno prima) → restart esterno (probabile reboot VM Hetzner; docker/dmesg non ispezionabili da dentro). Le sessioni Claude girano come processi DENTRO il container → terminate tutte insieme. Nessuna perdita dati (bind mount persistente). Il cron ticket-agent è ripartito normalmente (OPEN_TICKETS.md riscritto alle 15:30 → conferma ripresa, benigno). Niente OOM/disco pieno.
1. Messa in sicurezza sorgenti (git)
HEAD era già = origin/main (4a85abe), ma c'era MOLTO lavoro non committato — incl. l'intero modulo RAG/KB di aprile mai entrato in git. Creati 5 commit su main (⚠️ SOLO LOCALI, NON ancora su Gitea):
1d13166[CHORE] .gitignore: esclude*.bak*,.backups/,.ssh-temp/(quest'ultimo proteggeva una chiave SSH privata!)c0bf7b6[DOCS] standard cross-suite + governance CLAUDE.md + registri agent1d934e4[FEAT] UI: guida.html, index-en, mobile-conversion, ai-assistant, bug-reporter, help/i18n9b53ca3[FEAT] MktgLead getJsonBody + import-feedback-to-nexus + seed demoa7a21fa[FEAT] Knowledge Base RAG (KnowledgeBaseController + VectorService/EmbedService/RagService + kb.html/js + SQL 012/013 + AIService::askWithRag + qdrant in compose)- 🔐 Scrub sicurezza: rimossa la API key Voyage hardcoded da
EmbedService.phpedocker-compose.yml(ora solo in.envgitignored + vault). NON è finita su Gitea. (Valutare rotazione della chiave: era in chiaro su disco, condivisa con sustainai.) - Rimossi 2 duplicati orfani:
application/controllers/{config.php,EmailService.php}(copie identiche dei canonici).
⚠️ TODO CRITICO: i 5 commit sono solo nel
.gitlocale. Per il backup su Gitea servegit-login(token, cache svuotata dal reboot) +git push origin main. Il push tentato è fallito per assenza token. Anche questo file (CONTEXT) e altre eventuali modifiche doc sono da committare/pushare.
2. Modifiche DB PRODUZIONE — utenti/org "Agile"
Scoperto che "Agile" = consulting_firms id 1 → "Agile Technology SRL" (unico studio nel sistema). Il super_admin vive in users.role (enum globale), NON in user_organizations.role.
- ✅ Creato utente Simon Fattori (id 330,
s.fattori@agile.software):role=org_admin,consulting_firm_id=1, utente LOCALE (no SSO,sso_identity_id=NULL), passwordFattori2026!@impostata e verificata conpassword_verify. - ✅ Tagliavini (id 326) declassato
super_admin→org_admin(resta firm 1).roleNON è sincronizzato dall'SSO → cambio stabile. Restano altri super_admin (3 admin@certisource, 4 benassati, 46 worker, 107 sim-b2b). - ✅ Creata organizzazione
Agile Technology SRL(id 129,entity_type=important,sector=ict_services,consulting_firm_id=1,voluntary_compliance=0) per dogfooding NIS2 su sé stessi. org_admin: Fattori (is_primary) + Tagliavini.firm_org_assignments7/8/9. Firm 1 ora gestisce 126/127/128/129. employee_counteannual_turnover_eurlasciati NULL → da completare in onboarding (servono per soglia dimensionale NIS2).- Tutte le scritture in transazione (commit OK), solo su
nis2_agile_db.
Accesso infrastruttura (IMPORTANTE per le prossime sessioni)
- ❌ Dal container devenv NON si raggiunge il DB di produzione (
nis2_user@172.18.0.7→ Access denied;localhostnon ha MySQL qui). - ❌ La chiave SSH documentata
docs/credentials/hetzner_keyè ASSENTE. - ✅ Usata la chiave SSH effimera
.ssh-temp/id_ed25519_nis2-agile_8h_20260529-120834(validità 8h da 12:08 → scade ~20:08 del 29/05) perssh root@135.181.149.254→mysql -h localhost -u nis2_user. Creds DB da/var/www/nis2-agile/.envsul server.
3. Integrazione analisi docs/nis2/ → prodotto (v1.7.0) — DEPLOYATA in produzione
L'utente ha caricato in docs/nis2/ mockup HTML di un sistema NIS2 + 5 PDF normativi ufficiali. Integrati nel prodotto e deployati live. Doc completo: docs/nis2/INTEGRAZIONE_COMPLETATA.md.
- Fase 1 Asset Relevance Scoring NIS2 0-100 a 6 criteri (GV.OC-04):
AssetScoringService.php, endpointassets/scoringGrid|{id}/score|relevantSystems, UIassets.html, registro stampabile (audit/relevantSystemsRegister). SQL020. - Fase 2 Tassonomia incidenti Determina ACN 164179/2025: IS-1..4 +
entity_obligationessential/important (Allegati 3/4). SQL021,IncidentController::create,AIService::classifyIncident. - Fase 3 PIR 5-Whys + metriche TTD/TTC/TTR + timestamp di fase auto. SQL
022(tabincident_pir), endpointincidents/{id}/metrics|pir. - Fase 4 Mapping NIST CSF 2.0 (43 controlli) reference-only:
audit/nistCsfMapping(no migrazione). - FONTI CERTE (richiesta utente: AI+help citano fonti certe):
application/config/nis2_sources.php(registry),AIService::authoritativeSourcesBlock()iniettato nei prompt (vieta riferimenti inventati), citazioni inhelp.js, ingest PDF normativi nella KB:scripts/ingest-nis2-sources.php→ 287 chunk in Qdrantnis2_kbscope SYSTEM (retrieval verificato). - Analisi concorrenza Evix:
docs/EVIX_ANALISI_CONCORRENZA.html(gap-driven, suite=prodotti Agile, vs GRC intl + NIS2 IT).
Eseguito su Hetzner: backup /root/backup_pre_v170_20260529_165447.sql; migrazioni 020/021/022 applicate; ingest 287 chunk; smoke test endpoint (401 ok).
⚙️ Scoperte infrastrutturali NON OVVIE (per prossime sessioni)
- 🔑
/projects/nis2-agile(devenv) e/var/www/nis2-agile(host) sono LO STESSO filesystem via bind mount → le modifiche ai file sono già live in produzione, scp/git-pull NON servono. Confermato: stessogit status. - 🐛 Qdrant IP drift:
nis2-qdrantnon ha IP statico in compose, era driftato172.21.0.5→172.21.0.3. L'IP era hardcoded inVectorService.phpE indocker-compose.yml(QDRANT_URL). Fix applicato: entrambi → hostnamehttp://nis2-qdrant:6333(drift-proof).appricreato (docker compose up -d app), vault/chiavi preservate, RAG web verificata. - ⚠️
docker.confhaclear_env = no→ php-fpm EREDITA le env (contraddice la vecchia nota CLAUDE.md su clear_env): per questo l'IP morto inQDRANT_URLrompeva la RAG web. CLI risolve l'hostname Qdrant via Docker DNS. - ℹ️
pdftotextpresente sull'host ma NON nel containernis2-app(alpine); Qdrant raggiungibile solo dal container (network), non dall'host. Strategia ingest: estrarre testo su host in cache.txt, far girare l'ingest nel container (legge.txt). - ✅ ARIA cablato: creato
AiController::ask(POST /api/ai/ask) →AIService::askWithRag. Il FAB ARIA (common.js) chiamava/api/ai/askma il controller NON esisteva → assistente AI era rotto, ora funziona e cita le fonti certe (verificato in prod:rag_used=True, sources = Ambiti/Determina).kb_uploaded_documentsnon esiste su questo DB (mig. KB 012-014 non applicate qui) → tracking MySQL KB saltato (best-effort), ma la ricerca legge da Qdrant. - 🐛🔑 DNS php-fpm (verificato con diagnostico fpm-fcgi reale, poi rimosso): nei worker php-fpm Alpine/musl, durante una request HTTP,
getenv('QDRANT_URL')ritorna false Egethostbyname/curl NON risolvono gli hostname Docker single-label (nis2-qdrant→ "Could not resolve host"). Funziona SOLO un IP letterale (172.21.0.3 → 200). In CLI invece getenv+gethostbyname funzionano. Perciò:VectorServicefallback = IP letterale172.21.0.3(fpm-safe, live via bind mount);QDRANT_URLcompose = hostname (per CLI/cron, drift-proof via gethostbyname). ⚠️ DRIFT: se nis2-qdrant viene ricreato e cambia IP, aggiornare il fallback inVectorService.php. Fix definitivo (non fatto, richiede recreate rete):ipv4_addressstatico per qdrant indocker-compose.yml(subnet 172.21.0.0/16; IP liberi es. .10). NB:clear_env=noin docker.conf NON basta a passare l'env ai worker (verificato getenv=false). - ℹ️ Esiste un
public/_dbg_voyage.php(debug Voyage, non mio) ancora presente in prod — valutare rimozione.
⚠️ TODO aperti (sessione 3)
- PUSH Gitea: 2 commit miei (
5c545eafeat +94d7867fix Qdrant) + i 5 dell'agente sono SOLO locali. Servegit-login(token) +git push origin main. Tentato, fallito per token assente. - Lavoro "prossimo" da AgileHub: l'utente l'ha menzionato ma senza allegare contenuto/ID ticket → da chiarire alla ripresa.
- Opzionale: cablare
askWithRaga endpoint web; valutare IP statico Qdrant in compose; applicare migrazioni KBkb_uploaded_documentsse serve la lista doc in UI.
SESSIONE MATTINA — 2026-05-29 (TRPG alignment)
Data: 2026-05-29 Durata: sessione molto lunga — progetto allineamento NIS2↔TRPG completato
Cosa abbiamo fatto
Progetto allineamento NIS2 ↔ TRPG (suite Evix)
Doc canonico: docs/GAP_TRPG_NIS2_ALIGNMENT.md
Analisi gap tra TRPG v1.54.1 e NIS2 v1.0.0 → 18 gap (G01-G18) raggruppati in 5 fasi. 6 decisioni utente confermate (§10 del doc). Tutte le 5 fasi completate in unica sessione. Version 1.0.0 → 1.5.0.
Fase 1 — SSO Federation (v1.1.0)
- Migration
015_sso_columns.sql: aggiungeusers.sso_identity_id,users.password_version - Nuovo
application/services/SsoHelper.php— client SSO dual-mode (cURL nativo, zero deps) AuthController::login()+changePassword()con conditional SSO (dietroSSO_MODE).envsu Hetzner + vault-stewardtier1__nis2-app__sso/internal_key(placeholder)- AgileHub Ticket #220 aperto a team AGILEHUB per estendere
sso-password-sync.sh - SSO_MODE=local di default → comportamento utente invariato
Fase 2 — Multi-device Sessions (v1.2.0)
- Migration
016_active_sessions.sql: tabellaactive_sessions(jti tracking) +refresh_tokens.session_jti BaseController::requireAuth()verifica jti + last_activity throttleBaseController::parseDeviceLabel($ua)— parsing UA-friendlylogin()genera jti + insert active_sessions,logout()revoca selettiva,changePassword()revoca altre sessioni- 3 nuovi endpoint:
GET/DELETE /auth/sessions[/{id}] - UI
settings.htmltab Sicurezza: card "Sessioni Attive" con device list + revoca
Fase 3 — Password Reset + Context Switch (v1.3.0)
- Migration
017_password_reset.sql: tabellapassword_reset_tokens(TTL 30 min, single-use) - Endpoint
POST /auth/forgot-password(risposta opaca anti-enumeration) +POST /auth/reset-password - Pagine HTML:
forgot-password.html,reset-password.html(con strength bar) login.html: link "Password dimenticata?" ora funzionante (era alert manuale)EmailService::sendPasswordReset()aggiunto- Endpoint
POST /auth/switchContextcon rotazione JWT (revoca old session + nuovo jti + organization_id claim) - Dropdown tenant in sidebar esposto a TUTTI gli utenti con ≥2 org (prima solo consulenti)
_switchOrg()in common.js ora chiama switchContext + setTokens
Fase 4 — Impersonate + Preferences + Versioning UI (v1.4.0)
- Endpoint
POST /auth/impersonate(super_admin o consulente stesso firm, TTL 1h, JWT conimpersonated_byclaim, audit log) - Migration
018_user_preferences.sql:users.theme/timezone/notif_email/notif_inapp - Endpoint
GET/PUT /auth/preferences - Sidebar footer mostra versione corrente, click → modal changelog
- common.js
_loadVersionFooter()fetcha/version.jsonal boot
Fase 5 — Branding + Auth-gate (v1.5.0)
- Migration
019_firm_branding.sql: tabellafirm_branding(logo/colori/brand name per consulting firm) BrandingController.php(NUOVO):GET /branding/current(auth opzionale),PUT /branding(super_admin o consulente)- common.js
_loadFirmBranding()applica CSS variables al boot public/js/auth-gate.jscopiato/adattato da TRPG (gate password client-side per documenti riservati)- G15 skip: simulator esistenti coprono demo flows
- G18 skip: refactor controller rinviato (~5gg investimento, valore tecnico)
Scoperta CRITICA durante Fase 3: topologia DB
Vedi MEMORY.md → project_db_topology.md per dettagli.
PHP-FPM nel container nis2-app (clear_env=yes + .env mancante in container) ricade su DB_HOST=localhost di default → connette al MySQL del HOST Hetzner, NON al container nis2-db.
Conseguenza: tutte le migration vanno applicate via:
mysql -u$DB_USER -p$DB_PASS -h localhost $DB_NAME < migration.sql
dal HOST Hetzner, NON docker exec nis2-db mysql.
Lo si è scoperto perché Fase 3 ha fatto 500 con "table password_reset_tokens doesn't exist" anche se la migration era stata applicata "con successo" — andava sul DB sbagliato.
File creati/modificati (riepilogo)
SQL (5 migration nuove, tutte applicate al DB host):
docs/sql/015_sso_columns.sqldocs/sql/016_active_sessions.sqldocs/sql/017_password_reset.sqldocs/sql/018_user_preferences.sqldocs/sql/019_firm_branding.sql
PHP nuovi:
application/services/SsoHelper.phpapplication/controllers/BrandingController.php
PHP modificati:
application/controllers/AuthController.php(+13 metodi)application/controllers/BaseController.php(requireAuth + parseDeviceLabel + generateRefreshToken)application/services/EmailService.php(+sendPasswordReset)application/config/config.php(+2 costanti)public/index.php(+12 route + branding controller)
HTML/JS:
public/login.html(link forgot-password)public/forgot-password.html(NUOVO)public/reset-password.html(NUOVO)public/settings.html(tab Sicurezza con sessions)public/js/common.js(version footer + branding loader + tenant switcher esposto)public/js/auth-gate.js(NUOVO)public/version.json(1.0.0 → 1.5.0)
Doc:
docs/GAP_TRPG_NIS2_ALIGNMENT.md(NUOVO, piano completo + stato esecuzione)
Backups creati (tutti con timestamp 20260529-*):
/projects/nis2-agile/application/controllers/*.bak.pre-{sso,sessions,fase3,fase4}-*/projects/nis2-agile/public/*.bak.pre-{sessions,fase3,fase4}-*- Hetzner
/var/www/nis2-agile/.backups/*
File deployati su Hetzner
Tutti via scp -i .ssh-temp/id_ed25519_nis2-agile_8h_*:
- 4 PHP in
/var/www/nis2-agile/application/controllers/ - 1 PHP in
/var/www/nis2-agile/application/services/ - 1 PHP in
/var/www/nis2-agile/application/config/ - 5 HTML/JSON in
/var/www/nis2-agile/public/ - 2 JS in
/var/www/nis2-agile/public/js/
Nessun docker restart eseguito (non necessario, bind mount + PHP-FPM rilegge ad ogni request).
Decisioni utente confermate (§10 doc)
- SSO_MODE iniziale:
local(switch a dual dopo ≥7gg validazione) - Backfill SSO: lazy on-login (no script massivo)
- Sessioni concorrenti: nessun limite
- TTL token reset: 30 min
- Cadenza version bump: MINOR per fase (eseguito 1.1.0 → 1.5.0)
- Branding white-label: mantenuto in Fase 5 (eseguito)
Problemi aperti / dipendenze esterne
- AgileHub Ticket #220 (priorità per switch a
dual): estenderesso-password-sync.shper includere DBnis2_agile_db. Status: OPEN, dispatchGroup=RESOLVER, dispatchProduct=AGILEHUB. - vault placeholder
tier1__nis2-app__sso/internal_key: contienePLACEHOLDER_REGISTER_WITH_TENANT_MS_BEFORE_ACTIVATING_DUAL_MODE. Va sostituito con chiave reale ottenuta dal Tenant MS prima del switch. docker compose up -d --force-recreate apprichiesto per attivare env vaultSSO_INTERNAL_KEYquando si passa a dual (oggi opzionale, defaults sani lato codice).SSO_MODE=local→dual: modifica una sola riga in.env. Da eseguire solo dopo aver risolto le 3 dipendenze sopra. Bumpare version dopo (oltre 1.5.0).
Prossimi passi consigliati
- Validare 1.5.0 in produzione con il primo utente reale (test password reset + sessions UI + tenant switcher)
- Attendere risoluzione AgileHub Ticket #220
- Ottenere SSO_INTERNAL_KEY dal Tenant MS
- Replace placeholder vault:
docker exec vault-steward node /app/cli/vault-cli.js migrate tier1__nis2-app__sso internal_key '<real>' - Cambiare
.envsu Hetzner:SSO_MODE=local→SSO_MODE=dual docker compose up -d --force-recreate appper ricaricare env- Smoke test login utente con identità SSO esistente
- Bumpare a 1.6.0 con changelog "SSO dual mode attivato"
File chiave da sapere
CLAUDE.md— single source of truth governancedocs/GAP_TRPG_NIS2_ALIGNMENT.md— piano 5 fasi + stato esecuzioneMEMORY.md(auto) →project_db_topology.md— lezione CRITICA su DB host vs containerMEMORY.md(auto) →project_alignment_trpg.md— stato progetto allineamento