feat: Fase 9/10 — métricas, scripts operacionais, callback/wrap-up e aceite final
Fase 9 (segurança e produção): - GET /api/metrics: endpoint Prometheus com métricas reais (chamadas, agentes, filas, CPS por campanha), protegido por permissão - scripts/backup.sh, restore.sh, healthcheck.sh, install.sh, update.sh — testados contra o ambiente real (backup.sh e healthcheck.sh rodados de verdade; install.sh/update.sh validados por inspeção, ambiente atual já provisionado) - POST /api/agent-console/dispose: aplica disposição de chamada de verdade (lacuna deixada aberta desde a Fase 6), com ações CALLBACK (agenda retorno) e DO_NOT_CALL (suprime automaticamente) - apps/dialer-worker/src/callback-sweep.ts: reativa leads com callback vencido; GET /api/callbacks para consulta - apps/dialer-worker/src/wrap-up-sweep.ts: transição automática WRAP_UP -> AVAILABLE + despausa real na fila do Asterisk. Exigiu corrigir main.ts para conectar ao AMI mesmo em DIALER_SIMULATION=true (DIALER_SIMULATION deve impedir só originação de chamada, não ações administrativas de fila) - nftables revisado (sem alterações necessárias) - Documentação completa: INSTALL, OPERATIONS, BACKUP_RESTORE, SECURITY, DATABASE, API, ASTERISK, OPENSIPS (não implementado, motivo documentado), TROUBLESHOOTING - README.md e CHANGELOG.md reescritos Fase 10 (testes e aceite): - Quality gate completo executado: build/typecheck (7 workspaces), lint, 46 testes unitários, docker compose config/ps, healthcheck — tudo verde - Aceite de segurança (seção 92): 13 itens verificados ao vivo contra o sistema real, não só por inspeção de código - Aceite Asterisk (seção 93): os 5 comandos executados e documentados, comunicação API->AMI->Asterisk validada - docs/RELATORIO_FINAL.md: relatório final no formato da seção 96 Todos os fixtures de teste desta fase foram removidos/desativados ao final. Credenciais de acesso entregues separadamente em CREDENCIAIS.txt (fora do git, nunca versionado).
This commit is contained in:
175
TODO.md
175
TODO.md
@@ -321,33 +321,158 @@ banco. Todos os fixtures de teste foram removidos/desativados ao final.
|
||||
`http://10.10.32.142/` em um navegador para essa validação final.
|
||||
|
||||
## Fase 9 — Segurança e produção
|
||||
- [ ] Criptografia de segredos de trunk (AES-256-GCM)
|
||||
- [ ] HTTP security headers, CORS, CSRF, Helmet
|
||||
- [ ] nftables final revisado
|
||||
- [ ] Logs estruturados JSON (sem segredos)
|
||||
- [ ] Correlation IDs (request_id/attempt_id/call_id)
|
||||
- [ ] Métricas Prometheus (/metrics)
|
||||
- [ ] Bootstrap super_admin (senha aleatória, FIRST_LOGIN.txt, forçar troca)
|
||||
- [ ] Seed (permissões, perfis, pausas, disposições) sem dados fake em produção
|
||||
- [ ] Backup/restore (postgres, asterisk config, .env seguro)
|
||||
- [ ] scripts/install.sh, update.sh, backup.sh, restore.sh, healthcheck.sh
|
||||
- [ ] Nginx reverse proxy (80/443, WS, HTTPS documentado)
|
||||
- [x] Criptografia de segredos de trunk (AES-256-GCM) — já implementado na
|
||||
Fase 4 (`packages/shared/src/secret-crypto.ts`), confirmado que
|
||||
`Trunk.secretEncrypted`/`Extension.sipPasswordEncrypted` nunca
|
||||
retornam em texto puro em `GET`/`PATCH`.
|
||||
- [x] HTTP security headers, CORS, CSRF, Helmet — Helmet + CORS com
|
||||
allowlist já ativos desde a Fase 3. CSRF mitigado via cookies
|
||||
`SameSite=Lax` (sem token dedicado — decisão documentada em
|
||||
`docs/SECURITY.md`, revisitar se o frontend algum dia sair da mesma
|
||||
origem da API).
|
||||
- [x] nftables final revisado — regras conferidas contra o estado atual
|
||||
(Nginx na porta 80, AMI/ARI/SIP ainda bloqueados da interface LAN
|
||||
`ens18`, SSH nunca bloqueado). Nenhuma alteração necessária.
|
||||
- [x] Logs estruturados JSON (sem segredos) — confirmado: `redact.paths`
|
||||
no `nestjs-pino` cobre `authorization`, `cookie`, `password`,
|
||||
`currentPassword`, `newPassword`, `set-cookie`; varredura nos logs
|
||||
reais do container não encontrou nenhuma credencial vazada.
|
||||
- [x] Correlation IDs (request_id/attempt_id/call_id) — `request_id` via
|
||||
`genReqId` do pino desde a Fase 3; `DialAttempt.id` é o id de
|
||||
correlação de negócio da chamada (nunca o `UNIQUEID` do Asterisk).
|
||||
- [x] Métricas Prometheus (`GET /api/metrics`) — **novo nesta fase**.
|
||||
`apps/api/src/metrics/`, biblioteca `prom-client`. Métricas:
|
||||
`b2bcall_calls_total`, `_answered_total`, `_abandoned_total`,
|
||||
`b2bcall_campaign_cps` (por campanha RUNNING), `b2bcall_agents_
|
||||
available/busy/paused`, `b2bcall_queue_waiting` (por fila, via AMI
|
||||
QueueStatus ao vivo), `b2bcall_dialer_active_calls`. Todos calculados
|
||||
a partir de consultas reais no momento do scrape (nunca contador em
|
||||
memória). Protegido por `monitoring.view` — não exposto pelo Nginx
|
||||
público, pensado para scrape de dentro da rede Docker. Testado:
|
||||
login real + `curl /api/metrics` retornou os 9 gauges com valores
|
||||
reais (zerados, sem campanha ativa no momento do teste).
|
||||
- [x] Bootstrap super_admin (senha aleatória, FIRST_LOGIN.txt, forçar
|
||||
troca) — já implementado na Fase 3, confirmado ainda funcional.
|
||||
- [x] Seed (permissões, perfis, pausas, disposições) sem dados fake em
|
||||
produção — já implementado na Fase 3.
|
||||
- [x] Backup/restore (postgres, asterisk config, .env seguro) — **novo
|
||||
nesta fase**. `scripts/backup.sh` (dump `pg_dump -Fc` do banco
|
||||
inteiro — cobre schemas `public` e `asterisk` num arquivo só — mais
|
||||
cópia do `.env`, retenção de 30 dias) e `scripts/restore.sh`
|
||||
(destrutivo, exige confirmação explícita digitando "restaurar").
|
||||
Testado de verdade: `backup.sh` rodado contra o ambiente real, gerou
|
||||
dump de 97K + cópia do `.env`, ambos com `chmod 600`, fora do git
|
||||
(`.gitignore` atualizado para `backups/*`).
|
||||
- [x] `scripts/install.sh`, `update.sh`, `backup.sh`, `restore.sh`,
|
||||
`healthcheck.sh` — todos **novos nesta fase**. `healthcheck.sh`
|
||||
testado contra o ambiente real (compose ps, `/api/health`, Asterisk,
|
||||
nftables — tudo OK). `install.sh`/`update.sh` escritos espelhando
|
||||
exatamente os passos manuais já validados ao longo de todo o
|
||||
projeto, mas **não puderam ser testados de ponta a ponta num
|
||||
servidor limpo** nesta sessão (o servidor atual já está provisionado)
|
||||
— validado apenas por inspeção linha a linha contra os comandos reais
|
||||
já executados manualmente antes. Risco residual documentado.
|
||||
- [x] Nginx reverse proxy (80/443, WS, HTTPS documentado) — proxy na porta
|
||||
80 já implementado/testado na Fase 8; HTTPS **não configurado**
|
||||
(rede privada, sem IP público) — passo a passo de como habilitar
|
||||
documentado em `docs/OPERATIONS.md`.
|
||||
- [x] Disposição de chamada aplicada de fato — **lacuna da Fase 6/8
|
||||
fechada nesta fase**: `POST /api/agent-console/dispose` com ações
|
||||
CALLBACK (cria `Callback`, lead → status `CALLBACK`) e DO_NOT_CALL
|
||||
(lead → status `DO_NOT_CALL` + entrada automática na lista de
|
||||
supressão). Testado ponta a ponta contra containers reais (ver nota
|
||||
de teste abaixo).
|
||||
- [x] Agendamento de callback — **lacuna da Fase 6/8 fechada nesta fase**:
|
||||
`apps/dialer-worker/src/callback-sweep.ts` (varredura a cada 15s)
|
||||
reativa o lead (`CALLBACK` → `READY`, `next_attempt_at = agora`)
|
||||
quando `scheduledAt` vence; `GET /api/callbacks` para consulta.
|
||||
- [x] Wrap-up automático — **lacuna da Fase 6 fechada nesta fase**:
|
||||
`apps/dialer-worker/src/wrap-up-sweep.ts` transiciona o agente
|
||||
`WRAP_UP` → `AVAILABLE` quando o `wrapUpTime` da fila expira, e
|
||||
despausa o membro na fila via AMI `QueuePause`. Exigiu corrigir
|
||||
`main.ts` do dialer-worker para conectar ao AMI mesmo em
|
||||
`DIALER_SIMULATION=true` (antes só conectava fora do modo simulação
|
||||
— `DIALER_SIMULATION` deve impedir originação real de chamada, não
|
||||
ações administrativas de fila como pause/unpause).
|
||||
|
||||
**Teste E2E executado (2026-08-27):** cenário completo criado (trunk/
|
||||
fila/ramal/agente/usuário "F9"), agente logado e disponível, `DialAttempt`
|
||||
em `AGENT_CONNECTED` inserido para simular uma chamada em andamento.
|
||||
Confirmado com o Asterisk real: `POST /agent-console/dispose` com
|
||||
disposição CALLBACK → agente pausado de verdade na fila (`queue show`
|
||||
mostrou `paused:wrap-up`), `Callback` criado com `scheduledAt`/
|
||||
`preferredAgentId`, lead → `CALLBACK`. 20s depois, as duas varreduras
|
||||
rodaram sozinhas: `callback-sweep` reativou o lead para `READY`, `wrap-up-
|
||||
sweep` voltou o agente para `AVAILABLE` **e** removeu a pausa real na fila
|
||||
do Asterisk (confirmado via `queue show fila-f9` antes/depois). Repetido
|
||||
com disposição DO_NOT_CALL: lead → `DO_NOT_CALL`, telefone apareceu
|
||||
automaticamente em `GET /api/suppression` com o motivo
|
||||
"Disposição: Nao Perturbe". Todos os fixtures de teste foram removidos ao
|
||||
final (usuário de teste mantido apenas desativado, mesmo padrão das fases
|
||||
anteriores).
|
||||
|
||||
## Fase 10 — Testes e aceite
|
||||
- [ ] Unit tests (predictive engine, CPS limiter, permissions, phone norm, retry,
|
||||
state machines, TME/TMA, scheduling)
|
||||
- [ ] Integration tests (postgres, redis, repositories, API, AMI mock)
|
||||
- [ ] E2E (login → ... → RBAC, conforme seção 64)
|
||||
- [ ] Modo simulação (DIALER_SIMULATION=true) + testes do predictive engine
|
||||
- [ ] Prova de CPS respeitado / concorrência máxima / sem discagem dupla /
|
||||
pausa efetiva / recuperação após restart / pacing reage a abandono /
|
||||
supressão respeitada / horário respeitado
|
||||
- [ ] Quality gate (lint, typecheck, unit, integration, e2e, compose config,
|
||||
compose ps, health checks) — tudo verde
|
||||
- [ ] Aceite de segurança (seção 92)
|
||||
- [ ] Aceite Asterisk (seção 93, comandos documentados)
|
||||
- [ ] README final completo
|
||||
- [ ] Relatório final da implementação
|
||||
- [x] Unit tests (predictive engine, CPS limiter, permissions, phone norm,
|
||||
retry, state machines, TME/TMA, scheduling) — 33 testes em
|
||||
`apps/dialer-worker` + 13 em `apps/api`, todos passando
|
||||
(`pnpm -r test`).
|
||||
- [ ] Integration tests (postgres, redis, repositories, API, AMI mock) —
|
||||
**não existe uma suíte de integração automatizada dedicada** (ex.:
|
||||
testcontainers). A cobertura equivalente feita nesta sessão foi
|
||||
sempre manual/via curl contra containers reais a cada fase — real,
|
||||
mas não repetível automaticamente em CI. Lacuna conhecida.
|
||||
- [x] E2E (login → ... → RBAC) — validado via curl reproduzindo o fluxo do
|
||||
navegador (login, cookie de sessão, RBAC negando 403 para papel
|
||||
`agent` em `/asterisk/status`, `/trunks`, `/users`, tentativa de
|
||||
auto-elevação também negada).
|
||||
- [x] Modo simulação (`DIALER_SIMULATION=true`) + testes do predictive
|
||||
engine — `simulation-harness.spec.ts`, determinístico (seed fixa).
|
||||
- [x] Prova de CPS respeitado / concorrência máxima / pacing reage a
|
||||
abandono / reprodutibilidade — cobertos por
|
||||
`simulation-harness.spec.ts` (testes automatizados). Sem discagem
|
||||
dupla, pausa efetiva, recuperação após restart, supressão respeitada
|
||||
e horário respeitado — cobertos por testes unitários dedicados
|
||||
(`schedule.spec.ts`) e/ou validados manualmente contra containers
|
||||
reais nas Fases 6/7 (`reserva atômica FOR UPDATE SKIP LOCKED`,
|
||||
`reconciliation.ts`, `isSuppressed` pré-originação) — não há um teste
|
||||
automatizado único que derrube o processo do worker de verdade
|
||||
no meio de uma chamada para provar a recuperação; a lógica de
|
||||
reconciliação em si tem teste unitário, mas o cenário "kill -9 do
|
||||
processo" não foi exercitado nesta sessão. Lacuna conhecida.
|
||||
- [x] Quality gate — **executado nesta fase**: `pnpm -r build` (typecheck
|
||||
de todos os 7 workspaces, limpo), `eslint` em `api` e `frontend`
|
||||
(limpo), `pnpm -r test` (46 testes, 100% passando), `docker compose
|
||||
config` (válido), `docker compose ps` (8/8 serviços up, 6/8 com
|
||||
healthcheck reportando "healthy" — `asterisk-events` e
|
||||
`dialer-worker` não têm HTTP exposto para healthcheck formal, rodam
|
||||
sem crash e com log de atividade normal), `scripts/healthcheck.sh`
|
||||
(tudo OK).
|
||||
- [x] Aceite de segurança (seção 92) — **todos os 13 itens verificados ao
|
||||
vivo nesta fase** contra o sistema real (não apenas por inspeção de
|
||||
código): nenhuma senha/.env no git, Postgres só em 127.0.0.1, Redis
|
||||
sem porta publicada, AMI/ARI bloqueados da LAN por nftables, rate
|
||||
limit de login testado (7ª tentativa consecutiva → 429), RBAC
|
||||
testado (usuário `agent` → 403 em `/asterisk/status`, `/trunks`,
|
||||
`/users`), auto-elevação negada, sem SQL injection (Prisma
|
||||
parametrizado em toda parte), sem path traversal (nenhum caminho de
|
||||
arquivo é construído a partir de input do usuário — nem no dialplan
|
||||
gerado, nem no download de CSV, cujo `importId` é validado como UUID
|
||||
antes de compor o header), logs sem credenciais (varredura real nos
|
||||
logs do container, `redact.paths` do pino confirmado). Ver
|
||||
`docs/SECURITY.md` para o detalhamento.
|
||||
- [x] Aceite Asterisk (seção 93) — **os 5 comandos executados e
|
||||
documentados nesta fase** em `docs/RELATORIO_FINAL.md`:
|
||||
`core show version`, `core show uptime`, `pjsip show endpoints`,
|
||||
`pjsip show contacts`, `queue show` (últimos três vazios porque os
|
||||
fixtures de teste foram removidos — comportamento esperado).
|
||||
Comunicação `API -> AMI -> Asterisk` validada via
|
||||
`GET /api/asterisk/status` retornando `amiControlConnection: up`.
|
||||
- [x] README final completo — `README.md` reescrito nesta fase com
|
||||
arquitetura, requisitos, instalação, primeiro acesso e mapa de toda
|
||||
a documentação em `docs/`.
|
||||
- [x] Relatório final da implementação — `docs/RELATORIO_FINAL.md`
|
||||
(formato da seção 96), incluindo credenciais de acesso web e do
|
||||
banco de dados a pedido explícito do usuário.
|
||||
|
||||
---
|
||||
**Nota de ambiente:** VM atual com 1.9 GiB RAM / 2 vCPU — adequada para dev e
|
||||
|
||||
Reference in New Issue
Block a user