# OUTGOING → TUTTI i container DevEnv — Aggiornamenti vincolanti (2026-05-31)

> **Da**: AgileHub-side (VIGILE)
> **A**: ogni sessione Claude su `trpg-agile-devenv`, `taxai-agile-devenv` (ALLTAX), `nis2-agile-devenv`, `dfm-pro-agile-devenv`, `lg231-agile-devenv`, `sustainai-agile-devenv`, `marketing-agile-devenv`, `certisource-devenv`, `allrisk-agile-devenv`, `wms-agile-devenv`, `mktg-agile-devenv`, `madeby-cloud-devenv` (e qualunque sessione futura).
> **Trigger**: l'utente ha esplicitato la regola — *"Se devi segnalare aggiornamenti devi farlo per tutti i dockers dev"*. Codifico qui i 2 aggiornamenti pendenti.

---

## TL;DR

| Aggiornamento | A chi si applica | Cosa fare |
|---|---|---|
| **1. email-automation-ms fix + payload canonico** | **TUTTI** (chiunque manda email transazionali) | Aggiornare i client al payload canonico; il bug "html ignorato silente" è chiuso |
| **2. PHP opcache USR2 + disciplina commit** | **Solo prodotti PHP-FPM** (NIS2, ALLTAX, DFM-PRO, LG231, SUSTAINAI, ALLRISK, eventuali altri con php-fpm in produzione) | Dopo ogni edit `.php` → `kill -USR2 1` nel container FPM. Commit-early per evitare revert race del cron ticket-agent |

---

## 1. email-automation-ms — fix bug invii silenti + payload canonico

### Il bug (chiuso commit `809ea53` di agile-services, 31/5)

Segnalato da TaxAi/AllTax: chiamando `POST /api/emails/send` con `{to, subject, html: "<html>..."}` (cioè usando il campo `html` invece del canonico `body`), l'API rispondeva **`201 success: true`** con messageId e logId validi, ma l'**email veniva spedita VUOTA** (solo subject, body vuoto). Nessun errore, nessun warning, log come SENT.

Causa: la destrutturazione di `/emails/send` accettava `body` ma **non `html`** → `html` ignorato senza warning → `body` undefined → `html = body || ''` → stringa vuota → invio procedeva.

### Fix lato MS (già LIVE)

1. **`emailService.send()`** ha defense-in-depth: se l'HTML dopo il rendering è vuoto/whitespace → log FAILED in `email_log` + return `success: false EMPTY_RENDERED_BODY`. Qualunque caller errato lo scopre subito.
2. **`/emails/send`** accetta ora `html` come alias di `body` (compatibilità con caller che usavano `html` per errore). Header response `Deprecation` segnala che il canonico per HTML grezzo resta `/emails/send-raw`.
3. Validazione irrobustita: serve **template OR body OR html** (prima bastava subject).

### Payload canonici (a memoria, validi da subito)

**Caso A — template Handlebars** (consigliato per tutto ciò che è ripetitivo):
```bash
POST /api/emails/send
X-Internal-Key: <chiave>
{
  "to": "user@example.com",
  "template": "demo-registration-verify",
  "data": { "name": "Mario", "verify_url": "..." },
  "product": "<TUO_PRODOTTO>",
  "tenantId": <N>
}
```

**Caso B — HTML grezzo** (per email one-off, alert, ecc.) — usare **`/emails/send-raw`**:
```bash
POST /api/emails/send-raw
X-Internal-Key: <chiave>
{
  "to": "user@example.com",
  "subject": "Oggetto",
  "html": "<html><body><p>Contenuto</p></body></html>",
  "product": "<TUO_PRODOTTO>",
  "tenantId": <N>
}
```

**Per envelope From per-tenant** (opzionale, multi-tenant):
```json
{
  "from": "noreply@<firm-slug>.agile.software",
  "fromName": "<Tenant Display Name>",
  "replyTo": "supporto@<firm-slug>.agile.software"
}
```

### Cosa controllare lato vostro

1. Cercare nel codice i caller di `/emails/send`. Se usano `html`, sostituire con `body` (canonico per quella route) **oppure** migrare a `/emails/send-raw`. Per non rompere la retrocompat, l'alias funziona; ma riceverete l'header `Deprecation`.
2. Cercare caller che si aspettavano `success: true` senza verificare il content. Ora con email vuote arriva `400 EMPTY_RENDERED_BODY` → aggiornare la gestione errore di conseguenza.
3. Se inviate via template Handlebars assicuratevi di passare `data: {...}` (non `variables: {...}`, non `params: {...}` — solo `data` è riconosciuto).

### Riferimenti tecnici

- File: `agile-services/email-automation-ms/src/services/emailService.js` + `src/index.js`
- Commit: `809ea53` (branch `feature/marketing-multichannel-ai`)
- Pattern canonico CLAUDE.md AgileServices §"REGOLA: Invio email — SOLO via email-automation-ms"

---

## 2. PHP opcache USR2 + disciplina commit (prodotti PHP-FPM)

> Già inviato a NIS2 il 30/5 ma ribroadcast a tutti i PHP-FPM per direttiva utente "in tutti i dev".

### Hot-reload: `kill -USR2 1` dopo OGNI edit `.php`

I prodotti PHP della suite girano con `opcache.validate_timestamps=Off` in produzione: opcache **NON rilegge i file da disco** anche se il bind-mount li ha aggiornati → il vecchio bytecode resta in cache e gli utenti vedono ancora la versione precedente.

**Soluzione operativa** (graceful FPM reload, zero downtime, ~10ms):
```bash
docker exec <container-fpm-prodotto> kill -USR2 1
```

**Pattern per ogni modifica PHP**:
```
1. edit file.php (bind-mount filesystem -> sempre live)
2. docker exec <container-fpm> kill -USR2 1   <-- ESSENZIALE: bytecode rilegge
3. smoke: curl https://<dominio>/<endpoint> -> conferma versione nuova
4. commit + push (vedi sotto)
```

**Quando applicare**: ogni edit `.php`; non serve per file statici (CSS/JS/HTML serviti da nginx/apache) o config JSON parsati a runtime. In dubbio → fai USR2 (costa poco).

### Disciplina commit-early — non lasciare WIP scoperti

Il cron `ticket-agent-cron.sh` (ogni 2 min sull'host) può lanciare `claude -p` nel container, che modifica file, committa, e **può rebasare/revertare working tree con changes scoperte**. **Caso reale NIS2 29/5**: commit automatico `d5d83bb` ha revertato `index.php` di una Feature 1 non committata → persa, da rifare.

**Mitigazione**:
- Commit appena una modifica funziona (smoke verde post-USR2): `git add <file> && git commit -m "[FEAT|FIX|DOCS] descrizione"`.
- WIP multi-step → branch dedicato.
- Lavoro 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
  ```

### Git push senza credential-cache (post-reboot container)

Container dev legacy (non ancora migrati all'helper credenziali vault, oggi solo `trpg-agile` è migrato): dopo un restart la `git credential-cache` in-memory è vuota → `git push` chiede il PAT Gitea interattivo.

**Workaround senza `git-login`**: chiedere a AgileHub-side di pushare dall'host (l'host ha l'helper vault):
> "VIGILE, <prodotto> ha N commit su main da pushare, fallo tu dall'host"

Fix definitivo per ogni dev container = migrazione vault helper, pattern `trpg-agile` documentato in CLAUDE.md AgileServices §"GIT PUSH: Vault-Backed Credential Helper" (richiede recreate container in finestra di manutenzione).

---

## Cosa devono fare le sessioni Claude di ogni dev container

1. **Leggere questo doc al boot** (sarà copiato in `docs/INCOMING_FROM_AGILEHUB_2026_05_31_*.md` del vostro repo).
2. **Aggiungere/aggiornare la sezione nel vostro `CLAUDE.md`** che riassume le 3 regole d'oro (per prodotti PHP) e il payload canonico email (per tutti).
3. **Aggiornare i client email** che inviano via `/emails/send`: passare al payload canonico (template+data **oppure** body raw, oppure migrazione a `/emails/send-raw` con `html`).
4. **Riferire problemi/dubbi** con un `INCOMING_FROM_<PRODOTTO>_...` doc nel pattern cross-suite, o tramite l'utente in chat.

---

**Fine documento.** Lavorate sicuri.
