# OUTGOING → NIS2 — Workflow di rilascio + hot-reload PHP (2026-05-30)

> **Da**: AgileHub-side (VIGILE)
> **A**: sessione Claude `nis2-agile-devenv` (e qualsiasi sessione futura che lavora su NIS2)
> **Trigger**: l'utente ha segnalato che NIS2 non aveva sistematizzato due lezioni critiche scoperte sul campo (hot-reload PHP + disciplina commit). Codifico qui il pattern così non si reimpara da zero.

---

## TL;DR — Le 2 regole d'oro

1. **Ogni modifica PHP richiede `kill -USR2 1` nel container.** Il bind-mount NON basta: `opcache.validate_timestamps=Off` in produzione → senza USR2 il vecchio bytecode resta in cache, gli utenti vedono ancora la versione precedente.
2. **Committa SUBITO ogni modifica funzionante.** Le modifiche non committate sono a rischio: il cron `ticket-agent` o altri agenti possono rebasare/revertare il working tree (è successo: commit `d5d83bb` ha revertato `index.php` di una Feature non ancora committata).

---

## 1. Hot-reload PHP — il pattern operativo

NIS2 (e tutti i prodotti PHP della suite con PHP-FPM + opcache) gira con `opcache.validate_timestamps=Off` per performance. Conseguenza: **opcache non rilegge i file da disco**, anche se il bind-mount li ha già aggiornati. Dopo ogni edit di `.php`:

```bash
docker exec <container-nis2-fpm> kill -USR2 1
```

Cosa fa USR2 su FPM: **graceful reload** dei workers (zero downtime, request in volo finiscono, i nuovi workers leggono il bytecode fresco). NON serve riavviare il container, NON serve `docker compose restart`.

**Pattern per ogni modifica PHP:**

```
1. edit file.php (sul bind-mount /var/www/nis2-agile/...)
2. docker exec <nis2-fpm> kill -USR2 1
3. smoke: curl https://nis2.agile.software/<endpoint> → conferma la nuova versione risponde
4. commit + push (vedi §2)
```

Se non sei sicuro del nome esatto del container FPM:
```bash
docker ps --format '{{.Names}}' | grep -iE 'nis2.*(fpm|app|web|php)'
```

**Eccezioni** (file che NON richiedono USR2):
- File statici (CSS, JS, immagini, HTML puro) → serviti da nginx/apache, niente cache PHP.
- File JSON/template letti a runtime senza require/include (es. config JSON parsato con `json_decode`).
- `version.json` (il polling client lo rilegge ad ogni richiesta).

In dubbio → fai USR2, costa ~10ms.

---

## 2. Disciplina commit — non lasciare modifiche scoperte

**Regola**: appena una modifica funziona (smoke verde dopo USR2), **commit immediato** anche se è una WIP. Niente sit-on di feature funzionanti nel working tree.

**Perché**: il cron `ticket-agent-cron.sh` ogni 2 minuti può:
- Prendere un ticket OPEN, lanciare `claude -p` nel container → l'agent modifica file, committa, potenzialmente rebase/revert se trova conflitti.
- Altri processi (sync, sso-password-sync, ecc.) possono toccare il working tree.

Lo conferma il caso reale di ieri: commit `d5d83bb` (agent automatico) ha revertato `index.php` di una Feature 1 NON ancora committata. Persa la modifica → rifatta da zero.

**Mitigazione**:
- Commit dopo OGNI step funzionante (`git add <file> && git commit -m "[FEAT] descrizione"`).
- Per work-in-progress di più step → branch dedicato (`git checkout -b feat/<nome>` poi merge in main quando completo).
- **Heads-up al cron**: se stai lavorando attivamente, crea il semaforo manuale per evitare che il cron parta:
  ```bash
  echo "USER=cristiano STARTED=$(date -Iseconds)" > /tmp/agent-working.lock
  # ... lavori ...
  rm /tmp/agent-working.lock
  ```
  Il cron salta i container con `/tmp/agent-working.lock` attivo.

---

## 3. Git push se la credential-cache è vuota (post-reboot o container fresco)

Il container NIS2 oggi è **legacy** (non ancora migrato al vault credential helper come `trpg-agile`). Quindi:
- `git push` chiede il PAT Gitea via `git credential-cache` (in-memory).
- Dopo un reboot/restart del container → cache vuota → push bloccato.

**Workaround immediato senza git-login interattivo**: chiedi a VIGILE/AgileHub-side di pushare dall'host (l'host ha già l'helper vault). Pattern usato il 29/5:
```
"Vigile, NIS2 ha N commit su main da pushare, fallo tu dall'host"
→ ssh host: cd /var/www/nis2-agile && git -c safe.directory=. push origin main
→ chown -R git:git .git (ripristina ownership)
```
Funziona perché `/var/www/nis2-agile` (host) = `/projects/nis2-agile` (container) = stesso bind-mount → i commit fatti nel container sono visibili all'host.

**Fix definitivo** (richiede ricreate container NIS2): migrazione vault helper, pattern `trpg-agile`. Da fare quando arriva una finestra di manutenzione.

---

## 4. Workflow di rilascio NIS2 — versione semplificata

NIS2 è **L1 master-shared** (single-instance, `nis2.agile.software`), NON L2 partial-SaaS come TRPG. Quindi **il workflow è molto più semplice di TRPG**:

| | TRPG (L2 partial-SaaS) | NIS2 (L1 master-shared) |
|---|---|---|
| Frontend | bind-mount master → propaga istantaneo a N tenant | bind-mount → 1 sola istanza, istantaneo |
| Backend | container `trpg-data` per ogni tenant + plan TSSP + agent | nessun container per-tenant, è il container master |
| Plan in `hub_upgrade_plans` | sì (TSSP pipeline) | **no** — NIS2 non è sulla TSSP |
| Build immagine + push registry | sì (per Cat 2) | **no** |
| Bump `version.json` | sì | **sì** (per il polling client di auto-refresh) |
| Commit + push Gitea | sì | **sì** |
| Tag git `vX.Y.Z` | sì | **sì** (igienico, anche se non strettamente necessario) |

**Workflow operativo NIS2 per una release**:
```
1. Edit codice (PHP + asset)
2. kill -USR2 1 sul container FPM (per i file .php)
3. Smoke test: curl/browse → verifica funziona
4. Bump app/version.json (versione + build_id + changelog)
5. git add . && git commit -m "[FEAT|FIX|DOCS] descrizione"
6. git tag vX.Y.Z   (opzionale ma consigliato)
7. git push origin main --tags  (via host se cache token vuota)
```

**NON serve**: rebuild immagine Docker, plan in `hub_upgrade_plans`, agent run, retag registry. Quelli sono pattern TRPG per gestire la propagazione a N tenant. NIS2 = 1 istanza, l'edit live è già la propagazione.

---

## 5. Riferimenti

- **Standard ticket cross-suite**: `nexus-ticket-ms` (porta 4213), routing rules, AI auto-resolve + AWAITING_USER_CONFIRMATION (vedi `docs/ARCHITETTURA_SISTEMA_TICKET.md`).
- **Pattern TRPG L2 partial-SaaS** (per capire la differenza): `memory/reference_trpg_l2_partial_saas_frontend_bindmount.md`.
- **Standard email cross-suite** (se NIS2 manda mail): `email-automation-ms` only, vedi `docs/STANDARD_EMAIL_RELAY.md`.
- **Vault per credenziali NIS2**: `tier1__nis2-app__anthropic`, `tier1__nis2-app__voyage` (già presenti).
- **Helper credenziali Gitea**: oggi NIS2 NON è migrato. Fix futuro = pattern `trpg-agile` in CLAUDE.md AgileServices §"GIT PUSH: Vault-Backed Credential Helper".

---

## 6. Cosa aggiungere al CLAUDE.md di NIS2

Quando hai una finestra, aggiungi al **vostro** CLAUDE.md (in `/var/www/nis2-agile/CLAUDE.md` o equivalente) una sezione "**Hot-reload + Release workflow**" con il riassunto delle §1, §2, §4. Così ogni nuova sessione Claude su NIS2 le ha in contesto al boot, senza doverle riscoprire.

---

**Fine documento.** Dubbi/feedback → rispondi con un `INCOMING_FROM_NIS2_...` doc nello stesso pattern, o pingami in chat tramite l'utente.
