# Contesto Ultima Sessione > Il 2026-05-29 ci sono state DUE sessioni: **pomeriggio** (questa, qui sotto) e **mattina** (TRPG alignment, più in basso). --- ## 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 agent - `1d934e4` [FEAT] UI: guida.html, index-en, mobile-conversion, ai-assistant, bug-reporter, help/i18n - `9b53ca3` [FEAT] MktgLead getJsonBody + import-feedback-to-nexus + seed demo - `a7a21fa` [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.php` e `docker-compose.yml` (ora solo in `.env` gitignored + 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 `.git` locale. Per il backup su Gitea serve `git-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`)**, password `Fattori2026!@` impostata e verificata con `password_verify`. - ✅ **Tagliavini (id 326)** declassato **`super_admin` → `org_admin`** (resta firm 1). `role` NON è 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_assignments` 7/8/9. Firm 1 ora gestisce **126/127/128/129**. - `employee_count` e `annual_turnover_eur` lasciati 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; `localhost` non 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) per `ssh root@135.181.149.254` → `mysql -h localhost -u nis2_user`. Creds DB da `/var/www/nis2-agile/.env` sul server. --- ## 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](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`: aggiunge `users.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 (dietro `SSO_MODE`) - `.env` su Hetzner + vault-steward `tier1__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`: tabella `active_sessions` (jti tracking) + `refresh_tokens.session_jti` - `BaseController::requireAuth()` verifica jti + last_activity throttle - `BaseController::parseDeviceLabel($ua)` — parsing UA-friendly - `login()` genera jti + insert active_sessions, `logout()` revoca selettiva, `changePassword()` revoca altre sessioni - 3 nuovi endpoint: `GET/DELETE /auth/sessions[/{id}]` - UI `settings.html` tab Sicurezza: card "Sessioni Attive" con device list + revoca #### Fase 3 — Password Reset + Context Switch (v1.3.0) - Migration `017_password_reset.sql`: tabella `password_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/switchContext` con 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 con `impersonated_by` claim, 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.json` al boot #### Fase 5 — Branding + Auth-gate (v1.5.0) - Migration `019_firm_branding.sql`: tabella `firm_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.js` copiato/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: ```bash 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.sql` - `docs/sql/016_active_sessions.sql` - `docs/sql/017_password_reset.sql` - `docs/sql/018_user_preferences.sql` - `docs/sql/019_firm_branding.sql` **PHP nuovi**: - `application/services/SsoHelper.php` - `application/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) 1. SSO_MODE iniziale: **`local`** (switch a dual dopo ≥7gg validazione) 2. Backfill SSO: **lazy on-login** (no script massivo) 3. Sessioni concorrenti: **nessun limite** 4. TTL token reset: **30 min** 5. Cadenza version bump: **MINOR per fase** (eseguito 1.1.0 → 1.5.0) 6. Branding white-label: **mantenuto in Fase 5** (eseguito) --- ## Problemi aperti / dipendenze esterne - **AgileHub Ticket #220** (priorità per switch a `dual`): estendere `sso-password-sync.sh` per includere DB `nis2_agile_db`. Status: OPEN, dispatchGroup=RESOLVER, dispatchProduct=AGILEHUB. - **vault placeholder** `tier1__nis2-app__sso/internal_key`: contiene `PLACEHOLDER_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 app`** richiesto per attivare env vault `SSO_INTERNAL_KEY` quando 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 1. Validare 1.5.0 in produzione con il primo utente reale (test password reset + sessions UI + tenant switcher) 2. Attendere risoluzione AgileHub Ticket #220 3. Ottenere SSO_INTERNAL_KEY dal Tenant MS 4. Replace placeholder vault: `docker exec vault-steward node /app/cli/vault-cli.js migrate tier1__nis2-app__sso internal_key ''` 5. Cambiare `.env` su Hetzner: `SSO_MODE=local` → `SSO_MODE=dual` 6. `docker compose up -d --force-recreate app` per ricaricare env 7. Smoke test login utente con identità SSO esistente 8. Bumpare a 1.6.0 con changelog "SSO dual mode attivato" --- ## File chiave da sapere - `CLAUDE.md` — single source of truth governance - `docs/GAP_TRPG_NIS2_ALIGNMENT.md` — piano 5 fasi + stato esecuzione - `MEMORY.md` (auto) → `project_db_topology.md` — lezione CRITICA su DB host vs container - `MEMORY.md` (auto) → `project_alignment_trpg.md` — stato progetto allineamento