nis2-agile/CLAUDE.md
AgileHub VIGILE 306960cbf0 docs(handover): standard apple-developer-multi-prodotto v1.0 (broadcast 31/5)
Una sola iscrizione Apple Developer per tutta la suite, Team 5W6WYDQKTS,
Bundle ID convention it.<prodotto>.app, credenziali Apple nel vault sotto
tier1__shared-apple__developer/. Onboarding nuovo prodotto mobile in 30 min.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 17:10:34 +02:00

1058 lines
58 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- STANDARD:timezone-conventions:v1.0:start -->
## ⏰ ORARI E TIMEZONE — REGOLE OPERATIVE
> **TL;DR**: l autorità del progetto è **`Europe/Rome`** (CEST estate UTC+2, CET inverno UTC+1).
> Quando scrivi un timestamp, **indica SEMPRE il suffisso TZ** (`CEST`/`CET`/`UTC`) oppure usa ISO8601 con offset (`2026-05-09T16:19:00+02:00`). **Mai** timestamp ambigui.
| Sistema | TZ | Output esempio |
|---|---|---|
| Host Hetzner | `Europe/Rome` | `Sat May 09 16:19 CEST 2026` |
| Container DevEnv (alcuni in drift UTC, vedi standard full) | misto | verifica con `docker exec <c> date` |
| Container produzione | `Europe/Rome` | CEST |
| MySQL `time_zone` | `SYSTEM` | `NOW()` CEST, `UTC_TIMESTAMP()` UTC |
| Apache log `%t` | `Europe/Rome` (locale) | `[09/May/2026:13:04:52 +0200]` |
| Node.js MS | UTC interno | `Date().toISOString()``Z` |
| Crontab Hetzner | `Europe/Rome` | `0 3 * * *` = 03:00 italiane |
**Regole**:
1. **Audit/sequenze cross-MS** → UTC obbligatorio (`2026-05-09T14:19:00Z`)
2. **Doc operativi/UI** → CEST/CET con suffisso esplicito
3. **DB store** → UTC, display → locale
4. **Cron critici**`CRON_TZ=UTC` o fuori finestra DST 02:00-03:00 locale
**DST Italia**: ultima domenica marzo (CET→CEST, ora 02:00 saltata) + ultima domenica ottobre (CEST→CET, ora 02:00-03:00 duplicata).
**Spec completa**: `STANDARD_TIMEZONE_CONVENTIONS.md` (slug `timezone-conventions` v1.0, owner VIGILE).
<!-- STANDARD:timezone-conventions:v1.0:end -->
## 🍎 STANDARD apple-developer-multi-prodotto 2026-05-31 (per chi fa app iOS)
> Standard cross-suite `hub_standards` id=23 v1.0. Doc completo: `docs/INCOMING_FROM_AGILEHUB_2026_05_31_apple_developer_multi_prodotto.md`.
**Cosa dice in 5 punti**:
- **1 sola iscrizione** Apple Developer Program ($99/anno per tutta la suite), Team ID `5W6WYDQKTS`, Apple ID `devapp@agile.software`.
- **Bundle ID convention**: `it.<prodotto>.app` (es. `it.alltax.app`, `it.dfm-pro.app`, `it.nis2.app`).
- **Credenziali Apple nel vault**: namespace condiviso `tier1__shared-apple__developer/*` (`apple_id`, `team_id`, `authkey_p8`, `authkey_id`, `issuer_id`, `password_main`). App vault dedicata `shared-apple-reader` con scope read-only.
- **Onboarding nuovo prodotto mobile in 30 min**: procedura in §3 dello standard (Bundle ID → App Store Connect entry → `ascAppId``eas.json``eas credentials` → primo build). Da quel momento il prodotto builda + submitta autonomo.
- ⚠️ **NON committare MAI** `.p8` o password Apple. Aggiungere al `.gitignore`: `*.p8`, `AuthKey_*.p8`, `apple-developer.txt`, `appstore-connect-api-key.txt`.
**Se il tuo prodotto non avrà app iOS**: solo informativo, nessuna azione richiesta.
**Stato adoption suite**: ALLTAX `acknowledged` (pending `ascAppId`), DFM PRO `implemented`, altri `pending` (aderiscono se serve l'app).
---
## 🔵 VAULT-STEWARD chain fix 2026-05-31 (informativo)
> Aggiunto da VIGILE. Doc completo: `docs/INCOMING_FROM_AGILEHUB_2026_05_31_vault_chain_fix.md`.
**Cosa è cambiato nel vault-steward** (impatta solo chi fa troubleshooting/audit del vault):
- `vault-cli.js audit [--last=N]` → ✅ **ora funziona** (era rotto con `Incorrect arguments to mysqld_stmt_execute`, bug LIMIT prepared param di mysql2).
- `vault-cli.js verify-chain` → ⚠️ mostra ancora `❌ Chain BROKEN at id=257` — è **storico** (1645 race break documentati dal 25/4 al 31/5 per race condition cronica in `audit.log()`), **NON tampering, NON attacco**. Fix applicato (transazione + `SELECT FOR UPDATE` per serializzare writer concorrenti). Da `id=23793` in poi la chain HMAC è di nuovo affidabile (entry marker `audit_chain_reanchor`).
- Le tue letture credenziali via wrapper vault al boot funzionano **identiche a prima** — zero cambiamenti lato API.
**Stress test post-fix**: 50 letture concorrenti → 0 nuovi race break (1647 → 1647 invariato).
**File modificati host-side**: `/opt/vault-steward/{audit.js, db.js, vault-cli.js}`, container ricreato con 2 nuovi bind-mount (downtime ~5s, nessun MS in regressione). Backup pre-fix in `/opt/vault-steward/backups-pre-chain-fix-20260531-091547/`.
---
## 🔴 AGGIORNAMENTI AGILEHUB 2026-05-31 (vincolanti)
> Aggiunto da AgileHub-side (VIGILE) su direttiva utente: "se devi segnalare aggiornamenti devi farlo per tutti i dockers dev". Doc completo: `docs/INCOMING_FROM_AGILEHUB_2026_05_31_email_send_fix_and_php_opcache.md`.
### 1. email-automation-ms — bug "invii silenti" CHIUSO (per TUTTI i prodotti che mandano email)
Bug: `POST /api/emails/send` con payload `{to, subject, html: "..."}` (campo `html` invece del canonico `body`) rispondeva `201 success:true` ma spediva email **vuota** (solo subject). Fixed (agile-services commit `809ea53`): defense-in-depth in `emailService.send()` + alias `html` su `/emails/send` + 400 EMPTY_RENDERED_BODY.
**Payload canonici da subito**:
- Template Handlebars: `POST /emails/send {to, template, data, product, tenantId}`
- HTML grezzo: `POST /emails/send-raw {to, subject, html, product, tenantId}` ← preferito per HTML diretto
**Da controllare lato vostro**: caller che usano `html` su `/emails/send` (oggi alias-compat con header `Deprecation`) → migrare a `/emails/send-raw`; gestire `400 EMPTY_RENDERED_BODY` nel codice client; assicurarsi di usare `data` (non `variables`) per i template.
### 2. PHP opcache USR2 + disciplina commit (solo prodotti PHP-FPM)
**Hot-reload obbligatorio**: dopo OGNI edit `.php``docker exec <container-fpm-prodotto> kill -USR2 1`. Bind-mount NON basta (`opcache.validate_timestamps=Off` → vecchio bytecode in cache, gli utenti vedono ancora la versione precedente). USR2 = graceful FPM reload, zero downtime, ~10ms.
**Commit-early**: appena una modifica funziona (smoke verde post-USR2), commit subito. Il cron `ticket-agent-cron.sh` può revertare WIP scoperte (caso reale NIS2 29/5: commit `d5d83bb` revertato `index.php` di una Feature 1 non committata). Per WIP attivo prolungato → semaforo manuale:
```bash
echo "USER=... STARTED=$(date -Iseconds)" > /tmp/agent-working.lock
# il cron salta i container con quel lock
rm /tmp/agent-working.lock
```
**Push se cache token vuota** (post-reboot container legacy, oggi solo `trpg-agile` migrato al vault helper): chiedere a VIGILE/AgileHub-side "pusha N commit dall'host" (helper vault). Fix definitivo = migrazione vault helper come `trpg-agile`.
---
## 🔴 ATTIVITÀ PRIMARIA — Workflow di rilascio + hot-reload PHP + disciplina commit
> **VINCOLANTE per OGNI modifica di codice su NIS2.** Aggiunto 2026-05-30 da AgileHub-side (VIGILE) dopo che la sessione NIS2 ha scoperto sul campo che il bind-mount NON serve codice "live" e che le modifiche scoperte vengono revertate.
**Le 3 regole d'oro** (doc completo in `docs/INCOMING_FROM_AGILEHUB_2026_05_30_release_workflow_hot_reload.md`):
### 1. Hot-reload PHP — dopo OGNI edit `.php`
```bash
docker exec <container-fpm-nis2> kill -USR2 1
```
Il bind-mount NON basta: `opcache.validate_timestamps=Off` → senza USR2 gli utenti vedono ancora il vecchio bytecode. USR2 = graceful FPM reload (zero downtime, ~10ms, request in volo finiscono).
⚠️ **La sezione "ARCHITETTURA: PHP-FPM con BIND MOUNT (LIVE)" più sotto è fuorviante**: il filesystem è live via bind-mount, ma il **bytecode servito** no, finché non fai `kill -USR2 1`. Aggiornare quella sezione a riflettere la realtà.
### 2. Commit immediato — niente modifiche scoperte
Appena una modifica funziona (smoke verde post-USR2):
```bash
git add <file> && git commit -m "[FEAT|FIX|DOCS] descrizione"
```
**Mai** lasciare modifiche scoperte nel working tree: il cron `ticket-agent-cron.sh` (ogni 2 min) può lanciare `claude -p` che rebase/reverta. **Caso reale 29/5**: commit `d5d83bb` (agent automatico) ha revertato `index.php` di una Feature 1 non committata → persa.
Per WIP attivo prolungato → semaforo manuale (il cron salta i container con quel lock):
```bash
echo "USER=cristiano STARTED=$(date -Iseconds)" > /tmp/agent-working.lock
# ... lavori ...
rm /tmp/agent-working.lock
```
### 3. Push via host se cache token vuota (post-reboot container)
Il container NIS2 è ancora legacy (non migrato all'helper credenziali vault come `trpg-agile`). Dopo un reboot del container la `git credential-cache` in-memory è vuota → push bloccato. Invece di `git-login` interattivo, **chiedi a VIGILE/AgileHub-side**:
> "VIGILE, NIS2 ha N commit su main da pushare, fallo tu dall'host"
L'host è migrato all'helper vault, prende il PAT Gitea automatico, pusha sul bind-mount condiviso, ripristina ownership `.git`. Fix definitivo = migrazione vault helper anche per nis2-agile (richiede recreate container).
### Workflow operativo per ogni modifica
```
edit → kill -USR2 1 → smoke (curl) → bump app/version.json → git commit → git push (via host se serve)
```
### Cosa NON serve (semplificazione rispetto a TRPG)
NIS2 è **L1 master-shared** (1 istanza, `nis2.agile.software`). NON servono: plan TSSP in `hub_upgrade_plans`, image Docker build, agent run, retag registry. Quelli sono pattern TRPG **L2 partial-SaaS** per propagare a N tenant — NIS2 ha 1 sola istanza, l'edit + USR2 **È** la propagazione. Tabella di confronto nel doc completo.
📄 **Doc completo**: `docs/INCOMING_FROM_AGILEHUB_2026_05_30_release_workflow_hot_reload.md`
---
# NIS2 Agile - Documentazione Progetto
## REGOLE DI GOVERNANCE (LEGGERE ATTENTAMENTE, aggiornate 2026-04-22)
> **Queste regole sono OBBLIGATORIE e non negoziabili.**
### REGOLA FONDAMENTALE: Gitea = SOLO Backup
> **Gitea e un BACKUP one-way (sorgente -> Gitea), NON la fonte di verita.**
> **Il webhook auto-pull e DISABILITATO su tutti i 13 repo dal 2026-04-22.**
>
> - Le modifiche che fai nel container sono GIA live su `/var/www/nis2-agile/` via bind mount
> - `/var/www/nis2-agile/` e la FONTE DI VERITA
> - NON proporre MAI "git pull da Gitea" per applicare modifiche
> - Per tirare giu qualcosa da Gitea serve richiesta esplicita dell utente
> - git push -> Gitea = OK (backup)
> - git pull da Gitea -> `/var/www/nis2-agile/` = NO (puo sovrascrivere modifiche vere)
### ARCHITETTURA: PHP-FPM in Docker con BIND MOUNT (LIVE)
> **Verificato 2026-04-22**: NIS2 gira con:
> - `nis2-app` (php-fpm) con bind mount **RW** su `/var/www/nis2-agile/application` e `/public`
> - `nis2-web` (nginx) con bind mount **RO** su `/public`
> - `nis2-db` (MySQL) per persistenza
> - `nis2-qdrant` (vector DB)
> - Apache esterno ha `DocumentRoot /var/www/nis2-agile/public`
**Cosa va LIVE ISTANTANEAMENTE:**
- File `.php` in `/var/www/nis2-agile/application/` -- PHP-FPM rilegge ad ogni request
- File in `/var/www/nis2-agile/public/` -- serviti da nginx (via bind mount :ro)
- File `.html/.css/.js` nel public -- live via nginx/Apache
**Cosa richiede azione (CHIEDI SEMPRE CONFERMA):**
- Modifiche a `docker/nginx.conf` -> `docker restart nis2-web`
- Modifiche al Dockerfile -> `docker compose build + up -d`
- Schema DB (`nis2-db`) -> SQL manuale
- Worker php cron/feedback -> attendono il prossimo run o restart
**Nota**: non serve MAI fare rebuild di nis2-app per cambi di codice PHP -- il bind mount :rw garantisce che php-fpm legga sempre la versione aggiornata.
### Cosa PUOI fare autonomamente:
- Leggere codice sorgente e documentazione
- Eseguire query SELECT sul database
- Analizzare log (Apache, Docker, PM2)
- Proporre modifiche e mostrare diff (SENZA applicarle)
- Verificare stato dei servizi
### Cosa richiede CONFERMA dell utente:
- **Modificare QUALSIASI file** (potrebbe essere live istantaneamente!)
- **git commit e git push** (e un backup, ma sempre da confermare)
- **Modifiche schema DB** (ALTER/CREATE/DROP TABLE)
- **INSERT/UPDATE/DELETE** su dati di produzione
- **Installazione dipendenze** (composer require, npm install)
- **Modifiche a configurazione** (.env, docker-compose.yml, vhost Apache)
- **docker compose build/restart**, **pm2 restart**, **systemctl** qualsiasi
### DIVIETI ASSOLUTI:
- **MAI fare git pull** da Gitea senza richiesta esplicita
- **MAI fare git reset --hard** o operazioni distruttive
- **MAI toccare altri progetti o container**
- **MAI modificare configurazioni di sistema** (Apache globale, PHP globale, MySQL root)
- **MAI cancellare dati** senza backup e conferma utente
- **MAI tentare deploy SSH/SCP** verso altri server
### Flusso CORRETTO per una modifica:
1. **Analizza**: leggi il codice, capisci il problema
2. **Proponi**: mostra le modifiche all utente (diff) SENZA applicarle
3. **Attendi conferma**: l utente decide se procedere
4. **Applica**: solo dopo conferma
5. **Distingui**: e live subito o serve un rebuild/restart? Di all utente chiaramente
6. **Verifica**: controlla che https://nis2.agile.software funzioni (se applicabile)
7. **Backup su Gitea**: git commit + push (solo dopo conferma utente)
### Se qualcosa va storto:
- **NON tentare fix distruttivi** (reset, force push, drop, rm -rf)
- **NON proporre `git pull`** come recupero
- Comunica il problema all utente con dettagli precisi
## PRIMA DI INIZIARE
- Leggi sempre questo file prima di iniziare qualsiasi lavoro
- Il progetto e' al **100% di completamento + Sprint Simulazioni + Audit Chain + Sistema Feedback AI** (~34.000 righe, 85+ file sorgente)
- 15 commit su main, tutto deployato e testato su Hetzner
- E2E test completati, bug fixing, Docker verificato, UI polished
> 3. `docs/CONTEXT_LAST_SESSION.md` - **Contesto ultima sessione (continuita cross-browser)**
>
> Poi dimmi cosa hai capito dello stato attuale e dove eravamo rimasti.
## A FINE SESSIONE
> **OBBLIGATORIO**: Prima di chiudere, aggiornare SEMPRE:
>
> Aggiorna `docs/CONTEXT_LAST_SESSION.md` con:
> - Data sessione
> - Cosa hai fatto in questa sessione
> - File creati o modificati
> - File deployati su Hetzner
> - Problemi aperti / errori non risolti
> - Prossimi passi consigliati
>
> Se hai modificato schema DB, architettura o URL, aggiorna anche CLAUDE.md.
## Panoramica
NIS2 Agile e' una piattaforma SaaS multi-tenant per supportare le aziende nella compliance alla Direttiva NIS2 (EU 2022/2555) e al D.Lgs. 138/2024 italiano. Include AI integration (Claude API) per gap analysis, generazione policy, classificazione incidenti e suggerimenti rischi.
Target: PMI, Enterprise, Consulenti/CISO.
## Stack Tecnologico
- Backend: PHP 8.4 vanilla (no framework, Front Controller pattern)
- Database: MySQL 8.x (nis2_agile_db)
- Frontend: HTML5/CSS3/JavaScript vanilla
- Auth: JWT HS256 (2h access + 7d refresh)
- AI: Anthropic Claude API (claude-sonnet-4-5-20250929)
- Server: Hetzner CPX31 (135.181.149.254)
- VCS: Gitea (git.certisource.it)
- URL Produzione: https://nis2.agile.software/
## Visibilita Cross-Project
### agile-services (Read/Write BIDIREZIONALE)
**IMPORTANTE**: Questo progetto utilizza i microservizi condivisi di **agile-services** con accesso **Read/Write bidirezionale**.
#### Regole di integrazione
1. **Leggere SEMPRE** `agile-services-istructio.md` nella root del progetto per capire come interagire con i servizi
2. **Puoi leggere E modificare** i file in agile-services (path locale: `c:\Projects\agile-services`, path container: `/projects/agile-services`)
3. I servizi disponibili includono: vault, document, payment, certificate, subscription, billing, practice, company, supplier, investigation, whatsapp
4. Le credenziali di accesso ai servizi sono documentate in `agile-services-istructio.md`
5. Ogni nuovo servizio creato per NIS2 deve essere registrato anche in agile-services per essere riutilizzabile
6. **agile-services puo' leggere e modificare file di NIS2** — la visibilita e' bidirezionale
#### Accesso
- Path locale: `c:\Projects\agile-services`
- Path container: `/projects/agile-services` (read-write)
- File istruzioni: `./agile-services-istructio.md` (nella root del progetto)
### trpg.agile (Read-Only)
NIS2 puo' **leggere** il codice di TRPG Agile per riferimento e pattern, ma **NON deve modificare** nessun file.
- Path locale: `c:\Projects\trpg.agile`
- **Permessi**: SOLO LETTURA
- **Scopo**: Consultare pattern UI/UX, architettura frontend, convenzioni codice
- **DIVIETO**: NON modificare, NON creare, NON cancellare file in trpg.agile
### lg231.agile (Read-Only)
NIS2 puo' **leggere** il codice di 231 Agile per riferimento e pattern, ma **NON deve modificare** nessun file.
- Path locale: `c:\Projects\lg231.agile`
- **Permessi**: SOLO LETTURA
- **Scopo**: Consultare architettura microservizi PHP/Slim 4, pattern multi-tenancy, struttura database
- **DIVIETO**: NON modificare, NON creare, NON cancellare file in lg231.agile
### sustainai.agile (Read-Only)
NIS2 puo' **leggere** il codice di SustainAI Agile per riferimento e pattern, ma **NON deve modificare** nessun file.
- Path locale: `c:\Projects\sustainai.agile`
- **Permessi**: SOLO LETTURA
- **Scopo**: Consultare pattern UI/UX (identico a NIS2), struttura frontend, convenzioni CSS/JS
- **DIVIETO**: NON modificare, NON creare, NON cancellare file in sustainai.agile
### Riepilogo Visibilita
| Progetto | Permesso | Direzione |
|----------|----------|-----------|
| **agile-services** | Read/Write | Bidirezionale (NIS2 <-> agile-services) |
| **trpg.agile** | Read-Only | NIS2 -> trpg (solo lettura) |
| **lg231.agile** | Read-Only | NIS2 -> lg231 (solo lettura) |
| **sustainai.agile** | Read-Only | NIS2 -> sustainai (solo lettura) |
> **REGOLA**: Non accedere MAI a progetti non elencati in questa tabella. Nel dubbio, chiedere all'utente.
## Regola Fondamentale
Il progetto NIS2 Agile e' COMPLETAMENTE ISOLATO dagli altri applicativi (CertiSource, AGILE_DFM). Database dedicato, utente dedicato, path dedicati. Non condividere MAI credenziali tra applicativi.
## Struttura Progetto
```
nis2.agile/
├── CLAUDE.md # Questo file
├── .env # Variabili ambiente (NON committare)
├── .gitignore
├── application/
│ ├── config/
│ │ ├── config.php # Costanti app, CORS, JWT, AI, rate limiting
│ │ ├── database.php # Classe Database (PDO singleton)
│ │ └── env.php # Caricamento .env
│ ├── controllers/ # 19 controller (tutti implementati 100%)
│ │ ├── BaseController.php # Auth JWT, multi-tenancy, JSON responses (576 righe)
│ │ ├── AdminController.php # Gestione piattaforma (super_admin)
│ │ ├── AssessmentController.php # Gap analysis e questionari NIS2 (80 domande)
│ │ ├── AssetController.php # Inventario asset e dipendenze
│ │ ├── AuditController.php # Controlli, evidenze, report, export CSV
│ │ ├── AuthController.php # Login, register, JWT, rate limiting
│ │ ├── DashboardController.php # Overview, score, deadlines, heatmap
│ │ ├── IncidentController.php # Incidenti Art.23 (24h/72h/30d) + email
│ │ ├── NonConformityController.php# NCR/CAPA non-conformità e azioni correttive
│ │ ├── OnboardingController.php # Wizard onboarding con visura/CertiSource
│ │ ├── OrganizationController.php # CRUD org, membri, classificazione NIS2
│ │ ├── PolicyController.php # Policy, approvazione, AI generation
│ │ ├── RiskController.php # Risk register, trattamenti, matrice, AI suggest
│ │ ├── SupplyChainController.php # Fornitori, valutazione, risk overview
│ │ ├── TrainingController.php # Corsi, assegnazioni, compliance formativa
│ │ ├── ServicesController.php # Services API (read-only, API Key + scope)
│ │ ├── WebhookController.php # CRUD api_keys + webhook_subscriptions
│ │ ├── WhistleblowingController.php # Segnalazioni anonime Art.32 NIS2
│ │ ├── NormativeController.php # Feed NIS2/ACN/DORA con ACK tracciato
│ │ └── FeedbackController.php # Sistema segnalazioni bug/UX con risoluzione AI autonoma
│ ├── services/ # 6 servizi
│ │ ├── AIService.php # Anthropic Claude API (gap, risk, policy, incident)
│ │ ├── EmailService.php # Email CSIRT, training, welcome, invite
│ │ ├── RateLimitService.php # Rate limiting file-based
│ │ ├── ReportService.php # Report esecutivo HTML, export CSV
│ │ ├── WebhookService.php # Delivery webhook HMAC-SHA256, retry 3x
│ │ └── FeedbackService.php # createReport, classifyWithAI, broadcastResolution
│ │ └── VisuraService.php # AI extraction PDF visura + CertiSource API
│ ├── models/ # (vuoto - logica nei controller)
│ └── data/
│ └── nis2_questionnaire.json # 80 domande gap analysis (10 categorie Art.21)
├── public/
│ ├── index.php # Front Controller / Router
│ ├── .htaccess # Rewrite rules + Authorization header
│ ├── api-status.php # Health check
│ ├── index.html # Landing page
│ ├── login.html # Login
│ ├── register.html # Registrazione
│ ├── onboarding.html # Wizard 5-step (visura/CertiSource/manuale)
│ ├── setup-org.html # Setup org (legacy, ora usa onboarding.html)
│ ├── dashboard.html # Dashboard principale
│ ├── assessment.html # Gap analysis wizard
│ ├── risks.html # Risk management + matrice 5x5
│ ├── incidents.html # Gestione incidenti Art.23
│ ├── policies.html # Policy management + AI generate
│ ├── supply-chain.html # Fornitori + assessment sicurezza
│ ├── training.html # Formazione + assegnazioni
│ ├── assets.html # Inventario asset
│ ├── reports.html # Report compliance + audit log
│ ├── settings.html # Impostazioni org/profilo/membri
│ ├── companies.html # Gestione aziende (consulente)
│ ├── architecture.html # Pagina architettura sistema
│ ├── admin/
│ │ ├── index.html # Admin dashboard
│ │ ├── organizations.html # Gestione organizzazioni
│ │ └── users.html # Gestione utenti
│ ├── css/
│ │ └── style.css # CSS principale (~1600 righe)
│ ├── js/
│ │ ├── api.js # Client API (270 righe, tutti gli endpoint)
│ │ ├── common.js # Utility condivise (sidebar, notifiche, etc.)
│ │ ├── i18n.js # Internazionalizzazione IT/EN
│ │ └── help.js # Help contestuale online
│ └── uploads/ # Upload directory (gitignored)
│ └── visure/ # PDF visure camerali
├── docker/
│ ├── Dockerfile
│ ├── docker-compose.yml
│ ├── nginx.conf
│ └── php.ini
└── docs/
├── sql/
│ ├── 001_initial_schema.sql # Schema DB completo (20 tabelle)
│ ├── 002_email_log.sql # Tabella email_log
│ ├── 003_voluntary_compliance.sql # ALTER organizations: voluntary_compliance
│ ├── 004_ncr_capa.sql # Tabelle non_conformities, corrective_actions
│ ├── 005_consultant_support.sql # ALTER user_organizations: ruolo consultant
│ ├── 006_security_improvements.sql # Indici performance + soft delete + trigger audit immutabile
│ ├── 007_services_api.sql # api_keys, webhook_subscriptions, webhook_deliveries
│ ├── 008_whistleblowing.sql # whistleblowing_reports, whistleblowing_timeline
│ ├── 009_normative_updates.sql # normative_updates, normative_ack (seed 5 aggiornamenti)
│ ├── 010_audit_hash_chain.sql # prev_hash, entry_hash, severity su audit_logs
│ └── reset-demo.sql # Reset dati demo (mantiene id<=4)
├── context/
│ └── CONTEXT_SCHEMA_DB.md
├── prompts/
└── credentials/
├── credentials.md
└── hetzner_key # SSH key per Hetzner
```
## Multi-Tenancy
- Ogni tabella dati ha `organization_id`
- Header `X-Organization-Id` per selezionare org attiva
- Ruoli: `super_admin`, `org_admin`, `compliance_manager`, `board_member`, `auditor`, `employee`, `consultant`
- `requireOrgAccess()` in BaseController verifica membership
- Super admin bypassa tutti i controlli di membership
## Flusso Utente
1. **Registrazione** → redirect a `onboarding.html`
2. **Onboarding** (5 step): Scelta metodo → Visura/CertiSource/Manuale → Dati aziendali → Profilo → Classificazione NIS2
3. **Login** → se ha org → `dashboard.html`, altrimenti → `onboarding.html`
4. **Dashboard** → navigazione sidebar a tutti i moduli
## Database (29 tabelle)
organizations, users, user_organizations, refresh_tokens, assessments, assessment_responses, risks, risk_treatments, incidents, incident_timeline, policies, suppliers, training_courses, training_assignments, assets, compliance_controls, evidence_files, audit_logs, ai_interactions, email_log, non_conformities, corrective_actions
Schema: `docs/sql/` (9 migrazioni: 001→009)
## Servizi
### AIService.php
- `analyzeGapAssessment()` - Analisi assessment con raccomandazioni
- `suggestRisks()` - Suggerimenti rischi per settore/asset
- `generatePolicy()` - Generazione bozze policy NIS2
- `classifyIncident()` - Classificazione e severity incidenti
- Modello: claude-sonnet-4-5-20250929, API: https://api.anthropic.com/v1/messages
### EmailService.php
- Notifiche CSIRT: early warning 24h, notification 72h, final report 30d
- Training assignment e reminder
- Welcome email, member invite
- Template HTML professionale con branding NIS2 Agile
### RateLimitService.php
- File-based (/tmp/nis2_ratelimit/)
- Login: 5/min, 20/h | Register: 3/10min | AI: 10/min, 100/h
### ReportService.php
- Report esecutivo HTML (stampabile come PDF)
- Export CSV: rischi, incidenti, controlli, asset (separatore ;, BOM UTF-8)
### VisuraService.php
- Estrazione AI da PDF visura camerale (Claude con document type)
- Fetch dati da CertiSource API (GET /api/company/enrich?vat=)
- Mapping ATECO → settore NIS2
### AuditService.php (NUOVO)
- Hash chain SHA-256: ogni record include prev_hash → entry_hash linkato
- `log()`: inserisce con hash catena, auto-severity (info/warning/critical)
- `verifyChain()`: verifica integrità per org, rileva record manomessi
- `exportCertified()`: export JSON con SHA-256 del contenuto, salva in audit_exports
- Adattato da lg231-agile/shared/audit-lib/src/AuditTrailService.php
### WebhookService.php (NUOVO)
- Delivery HMAC-SHA256 Stripe-like, header X-NIS2-Signature
- Retry 3x backoff (0s/5min/30min), log in webhook_deliveries
### FeedbackService.php (NUOVO)
- `createReport()`: INSERT + classifyWithAI() sincrono (10s timeout)
- `classifyWithAI()`: chiama AIService::classifyFeedback(), aggiorna DB silenziosamente
- `broadcastResolution()`: email a tutti i membri org via EmailService::sendFeedbackResolved()
- Worker autonomo: `scripts/feedback-worker.php` (cron 30min, docker exec nis2-agile-devenv + Claude Code CLI)
### simulate-nis2.php (ROOT — NUOVO)
- Script simulazione demo 3 aziende × 5 scenari (CLI + SSE streaming)
- Aziende: DataCore (IT/Essential), MedClinic (Sanità/Important), EnerNet (Energia/Critical)
- Scenari: Onboarding + Assessment | Ransomware Art.23 | Data Breach | Whistleblowing | Audit Chain
- Reset: docs/sql/reset-demo.sql (cancella org/utenti con id>4 e email %.demo%)
## Deploy
- **SSH**: `ssh -i docs/credentials/hetzner_key root@135.181.149.254`
- **Path server**: `/var/www/nis2-agile/`
- **Apache config**: `/etc/apache2/sites-enabled/nis2-agile-software.conf` (HTTP) + `nis2-agile-software-le-ssl.conf` (HTTPS, dopo DNS+certbot)
- **Deploy**: `cd /var/www/nis2-agile && git pull origin main`
- **DB**: Vedi `docs/DB_ACCESS.md` per credenziali (password in `.env`)
- **Attivazione SSL nis2.agile.software**:
1. Cloudflare: aggiungere `nis2.agile.software A 135.181.149.254` (proxy OFF — grigio)
2. Hetzner: `bash /opt/devenv/scripts/setup-nis2-agile-software.sh`
- **Vecchio dominio**: `nis2.certisource.it` resterà attivo finché redirect non è configurato dallo script
## Git
- **Repository**: https://git.certisource.it/AdminGit2026/nis2-agile
- **Token Gitea**: Configurato in git credential manager (non documentare qui)
- **Branch**: main
- **Commit format**: `[AREA] Descrizione`
### Cronologia Commit
```
7080695 [FEAT] Ruolo Consulente + Wizard Registrazione v2
ba21534 [DEPLOY] Migrazione a subdomain nis2.agile.software
92f9366 Merge branch 'main'
d3eac7c [CORE] Rimosso credenziali da CLAUDE.md + aggiunto docs/DB_ACCESS.md
a0fd543 [CORE] Aggiunto settings Claude Code con permessi ampi
0a73983 [FIX] Dockerignore: allow docker/php.ini for build context
4bd2326 [CORE] Aggiunto integrazione agile-services
52fd45f [FEAT] i18n IT/EN, Help Online contestuale, pagina Architettura
4e3408e [FEAT] Visura auto-fill, adesione volontaria, modulo NCR/CAPA
517cab7 [FIX] Fix annual_turnover field name in setup-org.html
68f8cab [POLISH] Docker setup fix + UI polish + project completion
bcc5a2b [FIX] E2E testing - fix router, EmailService, frontend data mapping
```
## API Endpoints Completi
Base: `/api/{controller}/{action}/{id?}` (su subdomain https://nis2.agile.software/)
### Auth: POST register, login, logout, refresh, change-password | GET me | PUT profile
### Organizations: POST create, classify | GET current, list, {id}/members | PUT {id} | POST {id}/invite | DELETE {id}/members/{sid}
### Assessments: GET list, {id}, {id}/questions, {id}/report | POST create, {id}/respond, {id}/complete, {id}/ai-analyze | PUT {id}
### Dashboard: GET overview, compliance-score, upcoming-deadlines, recent-activity, risk-heatmap
### Risks: GET list, {id}, matrix | POST create, {id}/treatments, ai-suggest | PUT {id}, treatments/{sid} | DELETE {id}
### Incidents: GET list, {id} | POST create, {id}/timeline, {id}/early-warning, {id}/notification, {id}/final-report, {id}/ai-classify | PUT {id}
### Policies: GET list, {id}, templates | POST create, {id}/approve, ai-generate | PUT {id} | DELETE {id}
### Supply Chain: GET list, {id}, risk-overview | POST create, {id}/assess | PUT {id} | DELETE {id}
### Training: GET courses, assignments, compliance-status | POST courses, assign | PUT assignments/{sid}
### Assets: GET list, {id}, dependency-map | POST create | PUT {id} | DELETE {id}
### Audit: GET controls, evidence/list, report, logs, iso27001-mapping, executive-report, export | PUT controls/{sid} | POST evidence/upload
### Onboarding: POST upload-visura, fetch-company, complete
### Admin: GET organizations, users, stats
### NCR/CAPA: GET list, {id}, stats | POST create, fromAssessment, {id}/capa, {id}/sync, webhook | PUT {id}, capa/{subId}
### Services API (X-API-Key): GET status, compliance-summary, risks-feed, incidents-feed, controls-status, assets-critical, suppliers-risk, policies-approved, openapi
### Webhooks: GET api-keys, subscriptions, deliveries | POST api-keys, subscriptions, subscriptions/{id}/test, retry | PUT subscriptions/{id} | DELETE api-keys/{id}, subscriptions/{id}
### Whistleblowing: POST submit, {id}/assign, {id}/close | GET list, {id}, stats, track-anonymous | PUT {id}
### Normative: GET list, {id}, pending, stats | POST {id}/ack, create
### Feedback: POST submit | GET mine, list, {id} | PUT {id} | POST {id}/resolve
## Stato Completamento
Tutti i moduli sono implementati e testati:
- [x] Test end-to-end: tutti gli endpoint API verificati
- [x] Bug fixing: router rewrite, EmailService parse fix, frontend data mapping
- [x] Docker setup: Dockerfile, docker-compose.yml, nginx.conf, php.ini verificati
- [x] UI polish: animazioni, skeleton loaders, mobile backdrop, print styles, a11y
### Bug Risolti (E2E Testing)
1. **Router /{id}/subAction** - Pattern matching riscritto completamente per gestire GET /assessments/1/questions, GET /organizations/2/members, etc.
2. **EmailService parse error** - PHP non supporta `??` dentro `{$var}` string interpolation, estratto a variabile
3. **Frontend data mapping** - Dashboard, Assessment, Onboarding avevano nomi campo diversi dal backend
4. **Field name mismatches** - annual_turnover→annual_turnover_eur, question_id→question_code, compliance_level→response_value
*Ultimo aggiornamento: 2026-02-20*
## Infrastruttura DevEnv
| Risorsa | Valore |
|---------|--------|
| **Container** | nis2-agile-devenv |
| **IDE** | https://certisource.it/dev-nis2-ide/ |
| **API** | https://certisource.it/dev-nis2-api/ |
| **Browser** | https://certisource.it/dev-nis2-browser/ |
| **Porte** | 8454 / 3046 / 3047 / 6091 |
| **Password IDE** | Nis2AgileDev2026! |
| **Produzione** | https://nis2.agile.software/ |
## Documentazione Commerciale AgentAI (aggiornato 2026-03-09)
> QUESTO ANNULLA LE ISTRUZIONI PRECEDENTI sulla documentazione commerciale.
> Standard completo: /opt/devenv/COMMERCIAL_STANDARDS.md
> Stato prodotti: /opt/agent-ai/hub/AGENTAI_SPECS.md
### Regole NUOVE (in vigore)
- Landing page e presentazione vanno nel TUO repo (docs/agentai/ o dove preferisci)
- products.json (/opt/agent-ai/hub/products.json) e SOLO un indice con URL assoluti
- Aggiorna products.json con https://tuo-dominio/path-al-file.html
- La landing DEVE includere un link/bottone per registrazione o richiesta informazioni
- mktg.agile.software legge products.json per campagne marketing
### Regole OBSOLETE (non fare piu)
- NON scrivere file in /opt/agent-ai/hub/landing/
- NON scrivere file in /opt/agent-ai/hub/presentations/
- NON usare path relativi in products.json
- Le directory landing/ e presentations/ nel hub sono VUOTE
### Workflow
1. Scrivi landing/presentazione nel tuo repo
2. Commit + push (backup su Gitea (webhook DISABILITATO dal 2026-04-22))
3. Chiedi conferma utente
4. Aggiorna products.json con URL assoluto
5. Verifica URL raggiungibile
### REGOLA
> SEMPRE chiedere conferma utente PRIMA di generare documenti commerciali.
## REGOLA: Sincronizzazione CLAUDE.md
- Dopo QUALSIASI modifica a: URL produzione, dominio, porta, path, schema DB, architettura -> **AGGIORNARE CLAUDE.md IMMEDIATAMENTE**
- CLAUDE.md e la "single source of truth" del progetto
- A fine sessione: verificare che CLAUDE.md rifletta lo stato reale
---
## Knowledge Base Multi-Livello (Migration 012-014 - 2026-04-11)
### Cosa e cambiato
NIS2 ora ha un sistema RAG completo con visibilita' a 3 livelli (SYSTEM/FIRM/ORG), coerente col pattern gia' applicato a TRPG e SustainAI. L'AI puo' rispondere alle domande pescando da documenti caricati dai consulenti o dai responsabili compliance.
| Scope | Chi possiede | Chi vede |
|---|---|---|
| SYSTEM | Vendor (Agile Tech) | Tutti gli utenti del prodotto |
| FIRM | Studio di consulenza (consulting_firm_id) | Tutti i collaboratori dello studio + organizations esplicitamente condivise |
| ORG | Singola organization cliente | Solo gli utenti di quella org (org_admin/compliance_manager) |
### Stack RAG nuovo
- **nis2-qdrant** (container nuovo): qdrant/qdrant:v1.7.4, network nis2-net, IP fisso 172.21.0.5 (workaround DNS musl Alpine - vedi sotto).
- Voyage AI embeddings (`voyage-3-lite`, 512 dim, output_dimension=512). Chiave shared con sustainai.
- Collection Qdrant: `nis2_kb` (Cosine, 512 dim).
### Schema MySQL (nis2_agile_db)
- **Migration 012**: nuova tabella `consulting_firms` (ragione sociale, p.iva, plan, max_organizations, max_users, status). ALTER `users.consulting_firm_id` e `organizations.consulting_firm_id`.
- **Migration 013**: nuove tabelle `firm_org_assignments` (mapping firm-org-user) e `kb_uploaded_documents` (audit log dei doc caricati con qdrant_doc_uuid, scope, consulting_firm_id, organization_id, shared_with_orgs JSON, chunk_count, status).
### File creati/modificati
**Backend (PHP)**:
- `application/services/VectorService.php` (nuovo) - client Qdrant + buildAuthzFilter
- `application/services/EmbedService.php` (nuovo) - client Voyage AI
- `application/services/RagService.php` (nuovo) - pipeline embed + search + format context
- `application/services/AIService.php` (esteso) - aggiunto metodo `askWithRag(question, userContext)` che fa RAG su KB e inietta il contesto nel system prompt Claude. Fallback graceful se RAG non disponibile.
- `application/controllers/KnowledgeBaseController.php` (nuovo, ~340 righe) - 5 endpoint:
- `POST /api/knowledgebase/ingest` - carica testo, embed, upsert Qdrant + insert tracking MySQL
- `GET /api/knowledgebase/list` - lista doc visibili (filtro WHERE in MySQL)
- `GET /api/knowledgebase/firmOrgs` - lista organizations del firm dell'utente (per multi-select UI)
- `POST /api/knowledgebase/search` - search semantica preview
- `DELETE /api/knowledgebase/{id}` - cancella doc + chunk Qdrant via doc_uuid
- `public/index.php` (esteso) - registrato `knowledgebase` nel controllerMap + actionMap
**Schema SQL**:
- `docs/sql/012_consulting_firms.sql` (nuovo)
- `docs/sql/013_firm_assignments.sql` (nuovo)
**Frontend**:
- `public/kb.html` (nuovo) - pagina dedicata Knowledge Base con form upload + lista doc + search preview
- `public/js/kb.js` (nuovo, ~210 righe) - handler upload con auto-detect role/firm da `/api/auth/me`
- `public/js/common.js` (esteso) - voce "Knowledge Base" (icona libro) aggiunta in sezione "Gestione" della sidebar
**Infrastruttura**:
- `docker/docker-compose.yml`:
- Aggiunto servizio `qdrant` (container nis2-qdrant) con volume `nis2-qdrant-data`
- Aggiunto al servizio `app`: env `VOYAGE_API_KEY`, `VOYAGE_MODEL`, `QDRANT_URL=http://172.21.0.5:6333`
- `.env`: aggiunte `VOYAGE_API_KEY=pa-...` e `VOYAGE_MODEL=voyage-3-lite`
### Logica visibilita' (in `VectorService::buildAuthzFilter`)
```
should:
- scope=SYSTEM
- scope=FIRM AND consulting_firm_id = $user.firm_id
- scope=FIRM AND shared_with_orgs CONTAINS $user.organization_id
- scope=ORG AND organization_id = $user.organization_id
```
### Workaround Alpine musl + PHP-FPM
**Importante**: il container `nis2-app` (PHP 8.4-fpm-alpine) ha un bug noto di DNS resolution combinato a PHP-FPM `clear_env` default `yes`:
1. PHP-FPM workers in HTTP context NON risolvono hostname Docker (es. `nis2-qdrant`) — `Could not resolve host`
2. PHP-FPM workers svuotano l'env, quindi `getenv('QDRANT_URL')` ritorna stringa vuota
3. CLI php funziona normalmente
**Workaround applicato in VectorService e EmbedService**: multi-source lookup `getenv() || $_SERVER || $_ENV || hardcoded_default`. L'IP 172.21.0.5 e' hardcoded come fallback per nis2-qdrant. Anche VOYAGE_API_KEY ha un default hardcoded.
**Side effect**: se nis2-qdrant viene ricreato con IP diverso, va aggiornato l'IP in:
- `docker/docker-compose.yml` env `QDRANT_URL`
- `application/services/VectorService.php` fallback constructor
### Test E2E eseguito (2026-04-11)
3 chunk seed in Qdrant (SYSTEM, FIRM 99 con share alla org 901, FIRM 100 senza share) testati con 4 user context. Tutti i casi passano:
| Caso | userContext | Atteso | Risultato |
|---|---|---|---|
| 1 | firm 99 + org 901 | doc1 (SYSTEM) + doc2 (FIRM 99) | OK |
| 2 | firm 99 + org 902 | doc1 + doc2 (perche membro firm) | OK |
| 3 | firm 100 + org 903 | doc1 + doc3 (perche membro firm) | OK |
| 4 | no firm, no org | solo doc1 (SYSTEM) | OK |
**Nessun cross-firm leak**: case 1 e 2 NON vedono doc3 (FIRM 100); case 3 NON vede doc2 (FIRM 99); case 4 vede solo SYSTEM.
### Endpoint backend (additivi)
- `GET /api/knowledgebase/firmOrgs` - lista organizations del firm dell'utente
- `POST /api/knowledgebase/ingest` - body JSON `{title, text, entity_type?, scope?, shared_with_orgs?, organization_id?}`
- `GET /api/knowledgebase/list` - lista doc visibili
- `POST /api/knowledgebase/search` - body `{query, top_k?}`
- `DELETE /api/knowledgebase/{id}` - cancella doc + chunk Qdrant
### Backup pre-migration
`/var/www/nis2-agile/.backups/kb_<timestamp>/` contiene: AIService.php, AuthController.php, public/index.php, docker/docker-compose.yml.
### Cosa NON e cambiato
- AuthController/JWT (NIS2 ricarica gia' user dal DB in `requireAuth()`, quindi `consulting_firm_id` e' disponibile automaticamente in `currentUser`)
- Tutti i controller esistenti (Risk, Asset, Incident, Policy, Whistleblowing, Feedback, ...)
- AIService metodi esistenti (`analyzeGapAssessment`, `suggestRisks`, `generatePolicy`, `classifyIncident`, ...) - aggiunto solo `askWithRag()`
- Nessun servizio nexus-* toccato
- Schema esistente (organizations, users, assessments, ...) - solo ALTER ADD COLUMN consulting_firm_id
### Rollback
1. mysql nis2_agile_db: `DROP TABLE kb_uploaded_documents; DROP TABLE firm_org_assignments; ALTER TABLE organizations DROP COLUMN consulting_firm_id; ALTER TABLE users DROP COLUMN consulting_firm_id; DROP TABLE consulting_firms;`
2. Drop collection Qdrant: `curl -X DELETE http://nis2-qdrant:6333/collections/nis2_kb`
3. Stop nis2-qdrant container: `docker compose stop qdrant && docker compose rm -f qdrant`
4. Ripristinare file da `/var/www/nis2-agile/.backups/kb_<timestamp>/`
5. `cd /var/www/nis2-agile/docker && docker compose up -d --force-recreate app`
## AgileHub — Agent AI Automatico (ticket-agent-cron)
> Un agent AI automatico (cron ogni 2 min) analizza i ticket aperti e propone/applica fix in questo container. Queste istruzioni sono per TUTTI i prompt Claude che lavorano in questo progetto.
### Semaforo: `/tmp/agent-working.lock`
Se il file `/tmp/agent-working.lock` esiste, un agent sta lavorando su un ticket. **NON modificare file del progetto** finche il semaforo e attivo — rischio conflitto.
Contenuto del lock: `TICKET_ID=13 PRODUCT=TRPG STARTED=2026-04-13T06:02:00+00:00`
### Log modifiche automatiche: `AGENT_CHANGES.md`
Il file `AGENT_CHANGES.md` nella root del progetto contiene il log di TUTTE le modifiche applicate dall'agent automatico. **Leggilo ad ogni sessione** per sapere cosa e cambiato dall'ultima volta che hai lavorato qui.
### Come funziona il flusso ticket
```
1. Utente segnala problema (FAB supporto o voce)
2. Ticket creato su AgileHub → status OPEN
3. Agent (questo container) analizza il codice → propone fix (PLAN MODE, no modifiche)
4. Supervisore approva/rifiuta dalla app mobile AgileHub
5. Se approvato: agent applica il fix (BYPASS MODE) + aggiorna help/traduzioni/AI
6. Se rifiutato: agent rianalizza con le indicazioni del supervisore
```
### Regole per i prompt interattivi (come te)
1. **Prima di iniziare**: leggi `AGENT_CHANGES.md` per sapere cosa ha fatto l'agent di recente
2. **Controlla il semaforo**: `cat /tmp/agent-working.lock` — se attivo, aspetta o lavora su altro
3. **Dopo le tue modifiche**: se impattano funzionalita, aggiorna SEMPRE:
- `app/js/help.js` (help online contestuale)
- Traduzioni (IT + EN se il file e bilingue)
- Knowledge base AI (product_knowledge via API AgileHub)
4. **Non cancellare** `AGENT_CHANGES.md` — e il registro storico delle modifiche automatiche
5. **Messaggi al ticket**: se stai lavorando su un ticket, manda aggiornamenti con:
```
curl -s -X POST http://172.18.0.1:4213/tickets/{ID}/message \
-H "X-Internal-Key: nexus-internal-2026" \
-H "Content-Type: application/json" \
-d '{"content":"[aggiornamento]","role":"AGENT"}'
```
### API AgileHub (da dentro il container)
| Endpoint | Porta | Uso |
|----------|-------|-----|
| Ticket MS | `http://172.18.0.1:4213` | Ticket, routing rules, KB, support sessions |
| Tenant MS | `http://172.18.0.1:4214` | Auth, login, utenti |
| AI MS | `http://172.18.0.1:4211` | Sessioni AI, agent loop |
| Dashboard | `https://agilehub.agile.software` | UI web |
## REGOLA: SSO Single Sign-On (collegamento centralizzato)
> **Attivo dal 2026-04-15**. Ogni utente in questo prodotto ha un campo `sso_identity_id` nel DB che lo collega alla sua identita SSO centralizzata in AgileHub.
### Come funziona
- **`sso_identity_id`** nella tabella `users` = link stabile alla identita SSO
- **`password_version`** nella tabella `users` = contatore versione password
- Un **cron ogni 5 minuti** su Hetzner sincronizza `password_hash` e `password_version` dalla fonte SSO (`nexus_tenant_db.sso_identities`) al DB di questo prodotto
- **Non serve nessuna chiamata HTTP** tra container — tutto avviene via DB
### Cosa significa per te (agent AI)
1. **NON modificare `sso_identity_id`** — e un campo gestito dal sistema SSO
2. **NON modificare `password_version`** — e gestito dal cron sync
3. Se un utente cambia password da AgileHub, entro 5 minuti la nuova password funziona anche qui
4. Se modifichi il flusso di **cambio password** di questo prodotto, la modifica resta **solo locale** (non propaga agli altri prodotti)
5. Per propagare un cambio password a tutti i prodotti, il prodotto deve chiamare:
```
POST http://172.18.0.1:4214/auth/sso/change-password
Headers: Authorization: Bearer <jwt>, Content-Type: application/json
Body: {"currentPassword": "...", "newPassword": "..."}
```
Ma attenzione: questa chiamata richiede connettivita di rete al Tenant MS (porta 4214)
### Schema DB
```sql
-- Colonne aggiunte alla tabella users:
sso_identity_id INT NULL -- FK verso nexus_tenant_db.sso_identities.id
password_version INT DEFAULT 1 -- contatore, incrementa ad ogni cambio password SSO
```
### Documentazione completa
- Spec SSO: `/projects/agile-services/docs/SPEC_SSO_SINGLE_SIGN_ON.md`
- Istruzioni prodotti: `/projects/agile-services/docs/ISTRUZIONI_SSO_PRODOTTI.md`
- Cron sync: `/projects/agile-services/scripts/sso-password-sync.sh`
## REGOLA: Standard Versioning e Audit Trail
> **Standard centralizzato**: `GET http://172.18.0.1:4214/standards/standard_versioning` (sempre aggiornato)
**Regole obbligatorie:**
1. Ogni prodotto ha un file `version.json` (`app/` o `public/`) con formato SemVer: `{"version":"1.0.0","build":"...","date":"...","changelog":"..."}`
2. Il cron agent incrementa automaticamente il PATCH dopo ogni fix applicato
3. Lo sviluppatore incrementa MINOR (nuova funzionalita) o MAJOR (breaking change) manualmente
4. Ogni modifica software viene loggata nell audit trail: MAINTENANCE_ON/OFF, APPLY_START/END, VERSION_BUMP
5. Il bug reporter include automaticamente la versione in ogni segnalazione
6. **NON modificare version.json manualmente** durante un apply — il cron lo fa automaticamente
## REGOLA: Timezone Italia (Europe/Rome)
> **Standard centralizzato**: `GET http://172.18.0.1:4214/standards/standard_timezone`
**Regole obbligatorie:**
1. Tutti i container, script e servizi operano in timezone **Europe/Rome** (CET/CEST)
2. Ogni script bash deve avere `export TZ=Europe/Rome` in testa
3. I log devono mostrare ora italiana (leggibili senza conversioni)
4. Il frontend mostra date con `toLocaleString("it-IT")` o `{ timeZone: "Europe/Rome" }`
5. Il database salva in UTC — la conversione avviene in visualizzazione
## REGOLA: Cron su crontab Hetzner
> **Standard**: `GET http://172.18.0.1:4214/standards/standard_cron`
> **Registro**: `GET http://172.18.0.1:4214/standards/cron_registry`
**Regole obbligatorie per aggiungere un cron:**
1. Script in `/var/www/<prodotto>/scripts/<nome>.sh`
2. Log in `/var/log/<prodotto>-<nome>.log`
3. `export TZ=Europe/Rome` in testa allo script
4. Idempotente (rilanciabile senza danni)
5. Isolamento: tocca solo risorse del proprio prodotto
6. Aggiornare `docs/CRON_REGISTRY.md` in agile-services con la propria entry
7. Richiesta di aggiunta al crontab root tramite agile-services (no modifiche dirette)
## GIT PUSH: Nuovo Flusso (aggiornato 2026-04-24)
> **IMPORTANTE**: dal 2026-04-24 il token Gitea NON e piu persistente nel container per motivi di sicurezza.
### Come fare git push
```bash
# 1. Prima del push: imposta il token (cache 1h in memoria, NON su disco)
git-login
# (inserisci il Personal Access Token quando richiesto)
# 2. Ora puoi pushare
git push origin main
# 3. Opzionale - cancella subito il token dalla cache
git credential-cache exit
```
### Perche questo cambio
Se un attaccante compromette questo container, NON trova piu il token Gitea salvato in `/root/.git-credentials`. Prima era in chiaro e avrebbe permesso push su tutti i 20 repository.
Ora il token:
- NON e su disco
- E in memoria per max 1 ora dopo git-login
- Viene perso alla chiusura della sessione bash
### Se il token Gitea e stato compromesso
Rigenerarlo su Gitea: `git.certisource.it -> User Settings -> Applications -> Generate Token`
### Regola
**NON persistere MAI il token Gitea in file come `.git-credentials`, `.netrc`, script con password in chiaro.** Usa sempre `git-login` per la sessione corrente.
---
## Vault-Steward — Credenziali Centralizzate
> Guida completa: `/opt/devenv/VAULT_STEWARD.md` (montato ro nei container dev)
**Cosa cambia per questo progetto** (dal 2026-04-25):
- Le chiavi API esterne (Anthropic, Voyage, Tavus, LiveKit, ecc.) NON vivono piu nel `.env` — sono nel vault-steward (container Docker su Hetzner) cifrate AES-256-GCM.
- Il container del MS riceve le chiavi al boot tramite wrapper entrypoint (`/opt/devenv/scripts/vault-entrypoint.sh`) che fetcha dal vault e setta le env var prima di avviare apache/uvicorn/node.
- **MS di questo progetto migrati**: nis2-app
- **Token applicativo**: `VAULT_APP_TOKEN_<APP>` in `infrastructure/.env` (o equivalente)
- **Dual-mode**: se vault giu, fallback automatico a `.env` esistente (no down).
**Verificare wrapper attivo**:
```bash
docker logs <container> 2>&1 | grep vault-entrypoint
# atteso: [vault-entrypoint] Fetched N env vars from vault
```
**Aggiungere un nuovo MS al vault** (riassunto):
1. Migrare credenziali: `docker exec -e VAULT_VALUE=<v> vault-steward node /tmp/vault-repopulate.js tier1__<app>__<provider> <key>`
2. Registrare app: `docker exec vault-steward node cli/vault-cli.js register-app <app> tier1__<app>__*` (salva token!)
3. Modificare `docker-compose.yml`: aggiungi `entrypoint`, `command`, mount wrapper, env VAULT_*, network `vault-net`
4. Recreate container: `docker compose up -d --force-recreate <service>`
**Limitazioni note**:
- `docker exec <ms> env` mostra env Docker originali, NON le chiavi vault-injected. Per verifica usare `cat /proc/1/environ | tr "\0" "\n"` o test via PHP/HTTP request.
**Backup pre-vault**: `/root/vault-backup-20260424_185029.tar.gz`. Rollback compose: `cp <project>/docker-compose.yml.bak.20260425-vault <project>/docker-compose.yml && docker compose up -d --force-recreate <service>`.
---
## STANDARD AgileHub: marketing-tenant-provisioning v1.4 (adottato 2026-04-26)
Doc canonico: `docs/STANDARD_MARKETING_TENANT_PROVISIONING.md` (sha256 `1d7ffaa20fa376b6...`)
Standard cross-suite per provisioning tenant nel modulo Marketing AgileHub. Versione **v1.4** introduce nuovo blocco AWE `AC30_MarketingTenantProvision` per orchestrazione atomica del provisioning marketing tenant (tenant create + DNS Cloudflare + DKIM + API key + idempotency H7).
**Cosa impatta questo prodotto**: se in futuro questo prodotto attiva il modulo Marketing AgileHub per i suoi clienti, segui §4.X "Provisioning DKIM per Marketing module" + §16 commands rapidi. Workflow esempio orchestrazione: `nexus-marketing-ms/docs/examples/ac30-tenant-provision-workflow.json`.
Status adoption: acknowledged 2026-04-26.
---
## STANDARD AgileHub: persona-conversational-rules v2.0 (acknowledged 2026-05-09)
> **Doc canonico autoritativo (AgileHub)**: `/var/www/agile-services/docs/STANDARD_PERSONA_CONVERSATIONAL_RULES.md` (sha256 `2bb0ebe4052b73fce752911db0665b1e3dcdeb673624529426624622caaae97f`)
> **Copia locale di questo prodotto**: `docs/standards/STANDARD_PERSONA_CONVERSATIONAL_RULES.md`
> **Registry**: `nexus_hub.hub_standards` id=15 v2.0 status=adopted, applies_to=`*`
> **Owner standard**: Agile AI (governance) + VOX (TTS/voice runtime) + PRISMA (UI Editor) + VIGILE (codice etico + audit GDPR)
### Cosa è
Standard cross-suite **vincolante** per la governance delle **persone digitali AI** (chatbot, avatar conversazionali, assistenti vocali) della suite Agile Software. Versione 2.0 introduce il **modello concettuale Persona Digitale = Persona Umana**: ogni avatar/agente AI è governato con lo stesso rigore di un dipendente umano (CV, foto, voce, codice etico, performance review, dismissione graceful).
### Schema dichiarativo a 14 categorie (`agent_constraints`)
Tutte le regole conversazionali vivono in DB (NO hardcoding nei controller):
1. `product_naming` — come si chiama il prodotto (no inventare aliases)
2. `tts_pronunciation` — pronuncia sigle (IPA + dizionario ElevenLabs)
3. `topic_scope` — in/out scope + risposte canoniche
4. `image_handling` — formato URL immagini RAG + divieti pronuncia path
5. `topic_playbook` — mapping topic → script + filtro immagini
6. `latency_optimization` — fast-path turni semplici
7. `format` — vincoli output (max parole, no preamboli, ecc)
8. `code_of_conduct` — codice etico AI persona-specifico (transparency/GDPR/no deception)
9. `emotional_intelligence` — tono, archetipo, communication style
10. `conversation_memory` — cosa ricorda + scope persistence + GDPR Art.17 erasure
11. `escalation_policy` — quando/come passare a operatore umano
12. `performance_metrics` — KPI conversazione (CSAT, resolution rate, escalation rate)
13. `lifecycle_stage` — stage carriera (training/onboarding/operativa/review/dismissed)
14. `demo_sequence` — sequenze guidate multi-topic auto-advance
### Lifecycle persona digitale HR-grade (6 fasi)
1. **Assunzione** — creazione via Persona Composer wizard 6-step (Phase E LIVE)
2. **Onboarding** — formazione KB + skill assignment + smoke test 30 scenari
3. **Operatività** — live in produzione, monitoring SLA + audit log
4. **Growth** — espansione KB, retraining skill level (1-5)
5. **Performance Review** — audit periodico VIGILE (CSAT, drift detection, breach scan)
6. **Dismissione** — graceful: `active=false` + GDPR cascade erasure conversation history + tombstone audit
### Codice Etico AI — 9 principi vincolanti (Sez. 18 standard)
1. **Identity transparency** — dichiararsi AI quando esplicitamente chiesto
2. **No deception** — vietato fingere umana / inventare fatti / consulenza autoritativa fuori scope
3. **GDPR Art.13 disclosure** — disclosure su richiesta + apertura demo
4. **GDPR Art.22** — escalation umana per decisioni con effetti giuridici
5. **Voice clone consent doppio** — gate VIGILE (Phase G.A) per persona con `replica_id`
6. **Scope refusal cortese** — no echo parole problematiche
7. **Escalation loyale** — quando utente chiede umano, NO retention
8. **Audit log obbligatorio** — turni sensibili (legale/medico/compliance) loggati ≥ 90gg
9. **Sub-processor disclosure** — su richiesta, lista canonica (Anthropic/ElevenLabs/Tavus/...)
### Modello AgileHub: parallelismo umano-digitale
Ogni persona digitale ha mappatura 1:1 con un dipendente umano:
| Aspetto umano | Implementazione digitale |
|---|---|
| Nome+cognome | `agent_key` + `display_name` |
| CV | `digital_persona_skills` (skill+level 1-5) |
| Foto | `replica_id` Tavus o `avatar_image_url` |
| Voce | `voice_id` ElevenLabs + pronunciation_dictionary |
| Conoscenza | KB articles + RAG repository bindings (Phase D) |
| Esperienza | conversation_stream auto-ingest RAG |
| Codice etico | `code_of_conduct` constraint |
| Performance review | `performance_metrics` + audit VIGILE Q1/Q2/Q3/Q4 |
| Dimissioni | dismissione graceful + GDPR cascade |
### Cosa impatta NIS2 (Network and Information Security Directive)
Questo prodotto ha **1 persona digitale** governata da v2.0: **ARIA_SUPPORT_NIS2** (id=4) — assistente AI conversazionale supporto utenti NIS2. Stato: OPERATIVA in produzione.
### Stato adoption
`hub_standards_adoption` row INSERT 2026-05-09: `product_slug=NIS2`, `adoption_status=acknowledged` (riconoscimento standard senza migrazione persone proprie ancora). Implementation_notes: "Standard distribuito via INSTALLATORE pattern. Persone digitali del prodotto da migrare separatamente (Step 6 plan)."
### Cross-reference ad altri standard
- `installer-integration` v1.0 (id=1) — pattern distribuzione cross-suite
- `rag-platform` v1.0 (id=10) — knowledge platform per personaggi (binding via `rag_entity_bindings`)
- `gdpr-replica-consent` v1.0-DRAFT — consent doppio Phase G.A per voice clone
- `vault-steward-credential-management` v1.0 (id=7) — gestione voice_id/replica_id come credentials
---
## STANDARD AgileHub: multitenant-architecture v1.0 (adottato 2026-05-17)
Doc canonico: `docs/STANDARD_MULTITENANT_ARCHITECTURE.md` (sha256 `85c174fca6f9f905c2f8171741cf7f40d778c10bdefad8d7a27412903abb4030`)
Standard cross-suite NAVIGAI per piattaforma multitenant esplicita di AgileHub. Aggiunge tenant context propagation (JWT claims tenant_id+tenant_slug+is_master+tier additivi), visibility ENUM cross-tabella, opt-out granulare client da catalog master, billing per-tenant, observability tenant-aware.
**Cosa impatta questo prodotto**: se in futuro questo prodotto chiamerà API multitenant-aware di AgileHub (es. /api/marketing, /api/rag, /api/ai/personas), deve passare JWT con tenant_id + tenant_slug claims oppure header `X-Tenant-Slug`. Vedi §6 contracts shared lib `@agile/tenant-auth` per pattern integrazione (Node + Python).
Status adoption: acknowledged 2026-05-17.
---
## Integrazione analisi `docs/nis2/` — v1.7.0 (2026-05-29)
> Integrati i mockup + testi normativi PDF in `docs/nis2/`. Dettaglio e comandi deploy: `docs/nis2/INTEGRAZIONE_COMPLETATA.md`. **Migrazioni 020-022 e ingest KB DA ESEGUIRE su Hetzner** (host MySQL, non `docker exec nis2-db`).
### Nuove migrazioni (additive, idempotenti)
- `020_asset_relevance.sql` — assets += `relevance_score`, `relevance_criteria` JSON, `relevance_class`, `is_nis2_relevant`, `relevance_assessed_at/by`
- `021_incident_nis2_taxonomy.sql` — incidents += `nis2_incident_type` ENUM(IS-1..IS-4), `entity_obligation` ENUM(essential/important)
- `022_incident_metrics_pir.sql` — incidents += `triaged_at`/`contained_at`/`eradicated_at`/`recovered_at`; nuova tabella `incident_pir`
### Nuovi file
- `application/config/nis2_sources.php`**registry canonico FONTI NORMATIVE CERTE** (single source of truth AI + help)
- `application/services/AssetScoringService.php` — scoring rilevanza NIS2 0-100 (6 criteri, GV.OC-04)
- `scripts/ingest-nis2-sources.php` — ingest PDF normativi nella KB Qdrant `nis2_kb` scope SYSTEM
### Nuovi endpoint
- Assets: `GET /api/assets/scoringGrid`, `POST /api/assets/{id}/score`, `GET /api/assets/relevantSystems`
- Incidents: `GET /api/incidents/{id}/metrics`, `GET /api/incidents/{id}/pir`, `POST /api/incidents/{id}/pir`
- Audit: `GET /api/audit/nistCsfMapping`, `GET /api/audit/relevantSystemsRegister` (registro GV.OC-04 stampabile)
### REGOLA: Fonti certe (AI + help)
Ogni affermazione normativa di AI e help **deve citare** una fonte di `application/config/nis2_sources.php`.
`AIService::authoritativeSourcesBlock()` è iniettato nei system prompt e **vieta riferimenti inventati**.