# Gates de piloto — estado em 17/09/2026 O que precisa ser verdade antes de uma instância real de cliente passar a usar o AtendroZAP no lugar do provedor legado. Cada gate diz o que foi entregue, a evidência (teste, drill, registro de laboratório em [DECISIONS.md](DECISIONS.md)) e o que falta. "Lab" é o Compose com o número do responsável (`lab_20260916_1033`). Estados: **ok** (entregue e verificado), **parcial** (entregue, verificação incompleta), **pendente** (não entregue ou depende de terceiros). ## Serviço | Gate | Estado | Evidência | Falta | |---|---|---|---| | Ciclo de vida da instância no contrato legado (`init`, `connect`, `status`, `disconnect`, `restart`, `DELETE`, rotas por id) | ok | Etapas 1–2; lab 16/09 | — | | Envio (texto, mídia, contato, localização, menus) com idempotência e verdade de estado (`/message/status`) | ok | Etapas 3a–3c; lab 16–17/09 (imagem, texto, enquete, botões/listas nativos) | PTT com transcodificação (fora de escopo: áudio comum vai como arquivo) | | Recebimento, recibos, ações (reagir, editar, apagar, encaminhar, apagar para mim) | ok | Etapa 3c; lab 17/09 | Editar/apagar-para-todos a partir do telefone e reações de terceiro (segundo número) vistos ponta a ponta | | Webhooks: outbox ordenado, assinatura HMAC, retries, dead-letter, replay, breaker | ok | Etapa 4a; lab 17/09 (~15 h, 899 eventos, zero dead-letter) | Breaker só em teste unitário | | Envelope legado que o parser do consumidor aceita (`messages`, `messages_update`, `connection`, reação na forma `messages`, edição in-band) | ok | Fixtures da revisão do parser (`internal/webhook`); lab 17/09 | — | | Um dono por sessão: lease, cerca em toda escrita de estado, takeover automático | ok | Etapa 5a; teste de caos com dois managers em PostgreSQL real (adoção < 2×TTL); duas revisões adversariais | **Takeover com o número real** (matar o worker e ver outro adotar) — precisa de re-pareamento e autorização do responsável | | Keepalive: socket meio-aberto substituído em 90 s; diagnóstico do fechamento no log | parcial | Etapa 5a (testes unitários) | Ver no lab a causa das quedas a cada 20–30 min (`component=whatsmeow/socket`) — precisa de re-pareamento | | Drenagem no SIGTERM (HTTP, sessões, entregador) em 20 s | ok | `main.go`; Compose `stop_grace_period: 30s` | — | | Segredos em repouso (segredos de webhook, URLs de proxy) sob envelope key, rotação em três fases, boot recusado sem chave num banco selado | ok | Etapa 5a; lab 17/09 (`rows=1` reselado, coluna `enc1:…`, `restore-check` 1 legível/0 ilegíveis) | Produção precisa **definir e guardar** `ATENDROZAP_ENVELOPE_KEY` fora do banco | | Reemissão de token de instância com prazo de convivência | ok | Lab 17/09 (instância descartável: prazo de 120 s, revogação imediata, `400` fora da faixa) | — | | Proxy por instância no contrato do consumidor, endereços vetados, sondagem antes de gravar, transporte com timeouts, restart cruzado entre workers | ok | Etapa 5a; lab 17/09 (SOCKS5 do Compose: privado não listado → 400, senha errada → 400, `socks5h://…@proxy:1080` → 200, coluna selada) | **Sessão real através de proxy** — precisa de re-pareamento e autorização | | Backup e recuperação: drill de PITR (WAL + base backup + `recovery_target_time`) e restauração lógica verificada por `restore-check` | ok | `scripts/pitr_drill.sh` PASS local 17/09; `scripts/restore_drill.sh`; job `drills` no CI; runbook | Produção precisa de WAL archiving e base backup fora do host (o Compose de laboratório não tem) | | Grupos: lista, info, criação, membros, nome, imagem; assunto em `chat.name`/`groupName` | parcial | Etapa 5b (testes unitários/HTTP/storage; contrato verificado contra o consumidor) | **Nenhuma operação de grupo foi feita no lab** (sem sessão pareada; e criar/alterar grupos reais exige consentimento explícito) | | `/chat/find` e histórico do telefone em `/message/find` | parcial | Etapa 5b (testes); migração aplicada no lab | History sync real de um pareamento novo (volume, LIDs, tempo de importação) | | Admissão por worker e benchmark | parcial | `ATENDROZAP_MAX_SESSIONS`; `TestBenchmarkSessions` 10/50/150 sessões com motor falso | Carga com sessões reais; tabela `workers`/distribuição explícita | | Observabilidade: `/metrics` (sessões, lease, takeover, envios, recibos, outbox, keepalive, HTTP, banco), `X-Request-Id`, log sem identidades | ok | Etapas 4b/5a | Alertas (definir limiares: `outbox_oldest_pending_seconds`, `lease_renewals_total{result=lost}`, `takeovers_total`) | | Imagens-base fixadas por digest; inventário de dependências verificado no CI; revisão de licenças | ok | `Dockerfile`, `compose.yaml`, CI, drills; `scripts/check_dependencies.py`; [LICENSE_REVIEW.md](LICENSE_REVIEW.md) | Parecer jurídico para distribuição (GPLv3 em libsignal) — só necessário se o binário for distribuído | | Runbooks: PITR, rotação de chaves/tokens, takeover, proxy, quedas | ok | [RUNBOOKS.md](RUNBOOKS.md) | — | ## Produto (consumidor Atendro) Estes gates dependem de mudanças no consumidor, não deste serviço. | Gate | Estado | O que fazer | |---|---|---| | Verificar a assinatura `X-AtendroZAP-Signature` (`sha256=hex(HMAC(secret, timestamp + "." + body))`) no `uazapi-webhook` | pendente | Hoje o consumidor não valida assinatura nenhuma e resolve a instância por `instanceName`/`owner`; sem verificação, qualquer origem que conheça o nome injeta eventos | | Normalização do nono dígito | pendente | O WhatsApp guarda alguns números brasileiros sem o nono dígito; o consumidor compara dígitos literais em `contacts.phone` (resgate de envios ambíguos, `update-group-participants`). O serviço já resolve números em `/message/find` e nos destinos; o consumidor precisa tolerar as duas formas | | Allowlist de domínios (`uazapi.com` fixo em guard de URL, migration e media-proxy) | pendente | Liberar o host do AtendroZAP e o `ATENDROZAP_PUBLIC_URL` dos links de mídia | | `provider_type` e elegibilidade de piloto | pendente | Cadastrar o AtendroZAP como servidor com `provider_type` próprio (não como `uazapi` ativo) e escolher a instância piloto; `is_active=false` não devolve sessões ao backend anterior | | Cutover | pendente | Bloquear eventos tardios do backend antigo, invalidar cache de vínculo, reconciliar ACKs de envios anteriores | | `wa_lastMessageTime` / `wa_imagePreview` no branch `chats` do webhook | informativo | O serviço não emite `chats`; `/chat/find` devolve `wa_lastMsgTimestamp` e `wa_lastMessageTime` em segundos | | Paginação de `/chat/find` na importação de histórico | informativo | O consumidor lê uma página; o serviço devolve a lista inteira (até 5000) quando não há `limit` | ## Servidor 1 (VPS Hostinger, 17/09/2026) `1.atendro.cloud` (API) e `wpp.atendro.cloud` (console, em construção) apontam para a KVM1 provisionada por `deploy/provision.sh` (Docker, firewall 22/80/443, SSH só por chave, fail2ban) e `deploy/control` (PostgreSQL com WAL archiving, backup diário local, worker `vps1`, Caddy com TLS válido). `https://1.atendro.cloud/health/ready` responde `ready`. Imagem construída no próprio host a partir do commit (`deploy/ship.sh`). Pendente: copiar backups para fora do host; console; workers em outros hosts (WireGuard + TLS no banco). ## Laboratório: o que ainda precisa do responsável 1. **Re-parear** `lab_20260916_1033` (o telefone desvinculou o aparelho em 17/09 às 18:49; `POST /instance/connect` + QR). 2. Autorizar o **takeover com o número real** (matar o worker do Compose e subir um segundo worker apontando para o mesmo banco) e a **sessão real através do SOCKS5 do laboratório** (`POST /instance/proxy` na instância pareada). 3. Autorizar (ou não) **operações de grupo reais**: listar e consultar grupos é leitura; criar grupo, adicionar membros, renomear e trocar a imagem afetam terceiros e só serão feitos com consentimento explícito e num grupo de teste. 4. Segundo número para recibos e ações de terceiro ponta a ponta. 5. Decidir quando enviar os commits locais ao GitHub (16 à frente de `origin/main`).