# Evolution API - Fase 2 Monitor + Auto-Recovery

**Data:** 2026-08-10
**Executor:** Paulo (dev)
**Objetivo:** monitoramento ativo do Evolution API self-hosted com auto-recovery em 3 camadas e alertas Telegram via bot Mia. Serve qualquer instância WhatsApp que for criada nele (piloto TORQ na Fase 3, PX3 na Fase 5, futuros).
**Status:** Fase 2 concluída - script rodando via cron a cada 2min, teste de fumaça passou.

---

## Componentes entregues

| Item | Path |
|---|---|
| Script principal | `/opt/mia/scripts/evolution_healthcheck.py` (executável, dono mia) |
| Log JSON-linha | `/opt/mia/logs/evolution_monitor.log` |
| State persistente | `/opt/mia/logs/evolution_monitor_state.json` |
| Cron entry (user mia) | `*/2 * * * *` |
| Logrotate config | `/etc/logrotate.d/evolution_monitor` (mensal, 6 rotations, gzip) |
| Amostras de alerta | `/tmp/evolution_smoke/*.json` |

## Como funciona

1. Cron dispara `/usr/bin/python3 /opt/mia/scripts/evolution_healthcheck.py` a cada 2 minutos.
2. Script carrega credenciais de `/opt/mia/config/evolution_api.env` (permissão 600).
3. **Check container:** `docker inspect -f {{.State.Status}} evolution-climb-api`.
4. **Check instâncias:** `GET /instance/fetchInstances` + `GET /instance/connectionState/{name}` para cada.
5. **Classificação de estado:**
   - `open` -> healthy
   - `connecting` -> transient (não conta como falha imediata, mas incrementa contador)
   - `close` / `closed` / `unknown` / erro HTTP -> bad
6. **Recovery** só dispara quando `consecutive_bad >= 3` (6 minutos de falha real). Evita flap por instabilidade momentânea.
7. **Alertas Telegram** só em MUDANÇA de estado - primeiro alerta de falha (WARN), primeiro de sucesso pós-falha (RECOVERY), esgotamento das 3 camadas (CRIT com QR).

## Auto-recovery em 3 camadas

| Camada | Ação | Quando |
|---|---|---|
| **1 - leve** | `DELETE /instance/logout/{name}` + `GET /instance/connect/{name}` | Primeira tentativa. Aguarda até 10s por state=open. |
| **2 - média** | `sudo docker restart evolution-climb-api`, aguarda 30s | Se camada 1 falhar. |
| **3 - crítica** | Puxa QR via `GET /instance/connect/{name}`, salva PNG em `/tmp/evolution_qr_{name}_{ts}.png`, manda como foto pelo outbox | Se camada 2 falhar. Alerta CRIT + intervenção humana necessária. |

Backoff de 30s entre camadas evita cascata destrutiva.

## Alertas Telegram (mudança de estado apenas)

Formato do payload pro outbox `/opt/mia-bot/outbox/{timestamp_ns}.json`:

- WARN (`🟡`): primeira detecção de falha na instância
  ```json
  {"text": "🟡 [Evolution monitor] instância *torq-bruno* desconectou (state=close). Iniciando recovery automático."}
  ```
- CRIT (`🔴`): camada 3 esgotada, envia QR como foto
  ```json
  {
    "photo": "/tmp/evolution_qr_torq-bruno_1786377999.png",
    "caption": "🔴 [Evolution monitor] instância *torq-bruno* CAIU e não consegui religar sozinha.\nEscaneie o QR no WhatsApp em até 60s..."
  }
  ```
- RECOVERY (`🟢`): volta ao normal, mostra downtime
  ```json
  {"text": "🟢 [Evolution monitor] instância *torq-bruno* religada automaticamente pela camada 2 (offline ~4min)"}
  ```
- INFO (`ℹ️`): mudanças estruturais (instância nova detectada, instância removida)

**Anti-spam:** o script só envia UM alerta por transição de estado. Enquanto a condição persiste, apenas registra no log JSON, não notifica novamente.

## State persistente

Em `/opt/mia/logs/evolution_monitor_state.json`:

```json
{
  "container": {"status": "running", "since": "2026-08-10T15:58:04Z"},
  "instances": {
    "nome_da_instancia": {
      "last_state": "open",
      "health": "healthy",
      "consecutive_bad": 0,
      "since": "2026-08-10T15:58:04Z",
      "downtime_start": null
    }
  }
}
```

Se o arquivo sumir, o script recria - mas o próximo run inicia sem baseline (não vai spammar por isso graças à guarda `unknown` na comparação de estado do container).

## Log format (JSON-linha)

Cada linha em `/opt/mia/logs/evolution_monitor.log` é um evento JSON. Fácil de tail e grep:

```bash
tail -20 /opt/mia/logs/evolution_monitor.log
# filtrar só alertas enviados:
grep alert_sent /opt/mia/logs/evolution_monitor.log
# filtrar só quedas:
grep -E '"level": "(WARN|ERROR|CRIT)"' /opt/mia/logs/evolution_monitor.log
```

Eventos principais: `healthcheck_start`, `container_status`, `instance_check`, `alert_sent`, `docker_restart_start`, `docker_restart_done`, `recovery_l1_start/ok/fail`, `recovery_l2_start/ok`, `recovery_l3_start`, `qr_saved`.

## Teste de fumaça (executado 2026-08-10)

### Cenário 1: baseline - tudo ok
```
3 execuções consecutivas com container healthy
Resultado: zero alertas, apenas linhas INFO no log
```

### Cenário 2: container parado forçadamente
```
sudo docker stop evolution-climb-api
python3 /opt/mia/scripts/evolution_healthcheck.py

Log:
- container_status=exited detectado
- alert CRIT enviado ao outbox
- docker restart automático executado
- aguarda 30s
- container volta a running
- alert RECOVERY enviado ao outbox
- state atualizado
```

### Cenário 3: silêncio pós-recovery
```
Nova execução sem intervenção
Resultado: zero alertas, log limpo
```

Confirmado: pipeline detecta -> alerta -> tenta corrigir sozinho -> alerta recovery -> volta a ficar silencioso.

## Cron

Entrada aplicada em `crontab -u mia`:

```
# Evolution API healthcheck + auto-recovery (Fase 2)
*/2 * * * * /usr/bin/python3 /opt/mia/scripts/evolution_healthcheck.py >> /opt/mia/logs/evolution_monitor.log 2>&1
```

## Logrotate

`/etc/logrotate.d/evolution_monitor`:

```
/opt/mia/logs/evolution_monitor.log {
    monthly
    rotate 6
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
    dateext
    dateformat -%Y%m
}
```

Mantém 6 meses de histórico comprimido. Nome dos arquivos rotacionados: `evolution_monitor.log-YYYYMM.gz`.

## Requisitos e dependências

- **sudo NOPASSWD:** usuário `mia` já tem `ALL NOPASSWD ALL` (confirmado via `sudo -l`). O script usa `sudo -n docker inspect/restart` que funciona sem prompt.
- **Python 3:** apenas biblioteca `requests` (já instalada).
- **Outbox:** `/opt/mia-bot/outbox/` (o bot Mia consome e envia pro Telegram do Renato).
- **QR generation:** NÃO precisa da lib `qrcode`. O endpoint `/instance/connect` do Evolution v2 já retorna PNG em base64, decodificamos direto.

## Ajustes possíveis (não urgentes)

- **FAIL_THRESHOLD** (padrão 3 = 6min de falha real antes de recovery): baixar pra 2 se quiser mais agressivo.
- **BACKOFF_SECONDS** (30): aumentar se restart do container tá custando muito.
- **CONTAINER_BOOT_WAIT** (30): pode subir pra 45s em VPS mais lenta.
- Adicionar retenção separada dos state.json antigos se quiser auditar históricos.
- Integrar métricas em Prometheus/Grafana (não é escopo desta fase - hoje o log JSON é a métrica).

## Coexistência com o resto

- Não mexe em nenhum container que não seja `evolution-climb-api`.
- Não interfere no Dinastia Evolution (`dinastia_evolution.1.f77c...`), nem no `scheduler_evolution_api`.
- Usa apenas outbox Mia (não abre bot próprio, não cria novo endpoint).
- Log próprio isolado (nada compartilhado com outros pipelines).

## Próxima fase

**Fase 3:** criar instância piloto TORQ Bruno no Evolution, gerar QR pela primeira vez, apontar webhook pro consumidor torq-sdr. Aguardar sinal do Renato.
