26 KiB
Contesto Ultima Sessione
Il 2026-05-29 ci sono state DUE sessioni: pomeriggio (questa, qui sotto) e mattina (TRPG alignment, più in basso).
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.
→ Decisione aperta: (A) config in DB + secret inseriti separatamente nel vault via CLI (nessun secret nel prodotto); (B) tabella org_connectors con secret cifrato AES nel DB NIS2 (devia da "mai segreti nel DB applicativo"). companies.html è SANO (548 righe, usa OrganizationController). Nessun codice ancora scritto per questa feature.
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