nis2-agile/docs/CONTEXT_LAST_SESSION.md
DevEnv nis2-agile 0330bcf29d [DOCS] CONTEXT_LAST_SESSION: sessione 2026-05-29 pomeriggio (reboot, 5 commit, utenti/org Agile)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 17:19:05 +02:00

12 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 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_adminorg_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.254mysql -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

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.mdproject_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.


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=localdual: 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 '<real>'
  5. Cambiare .env su Hetzner: SSO_MODE=localSSO_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