prompt inicial do projeto

This commit is contained in:
2026-07-22 15:55:55 -03:00
commit 6fb920d333
19 changed files with 2937 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
# FASE 8 — Planos, módulos, LGPD e observabilidade
## Objetivo
Fechar o que torna o produto operável e vendável: módulos ligáveis por tenant, canal de WhatsApp preparado (interface, não integração), retenção e expurgo automáticos, direitos do titular, e os SLOs medidos de verdade.
## Pré-requisitos
FASES 07 concluídas. Ler `docs/06-LGPD-E-SEGURANCA.md` §3 e §4, e `docs/05-INFRA-DOCKER.md` §6.
## Tarefas
### 1. Canais de notificação plugáveis
```kotlin
interface NotificationChannel {
val key: FeatureKey? // null = sempre ativo
suspend fun notify(alvo: NotificationTarget, evento: NotificationEvent): Result<Unit>
}
```
`PushChannel` (FCM + APNs) sempre ativo. `WebSocketChannel` sempre ativo.
`NotificationDispatcher` resolve os canais habilitados para o tenant e despacha **em paralelo**, tolerando falha individual — um canal fora do ar não pode impedir os demais.
### 2. WhatsApp — interface e flag, sem integração
O WhatsApp é **módulo pago opcional**; o app é o notificador principal. Nesta fase entrega-se apenas:
- `WhatsAppChannel` com `key = FeatureKey.WHATSAPP_NOTIFICATIONS`, implementação **stub** que registra o que seria enviado
- Toggle no painel admin, com o efeito descrito em uma frase
- Estrutura de templates prevista
- Documentação em `docs/anexos/whatsapp-v2.md` com o que falta: WABA verificada, templates `utility` aprovados, custo (~US$0,004/msg no Brasil, grátis dentro da janela de 24h)
**Não integre com a Meta agora.** Verificação de negócio e aprovação de template levam dias e bloqueariam o lançamento. E registre no documento a limitação de fundo: a latência do WhatsApp não serve para *tocar* uma chamada — ele é aviso paralelo com deep link, nunca o canal primário.
### 3. Feature flags
```kotlin
@Service
class FeatureService {
fun habilitado(tenantId: UUID, key: FeatureKey): Boolean
fun dentroDaQuota(tenantId: UUID, key: FeatureKey, uso: Int): Boolean
}
```
Cache curto (60s) com invalidação ao alterar. Toda mudança de flag vai para `audit_log` com autor.
Quotas relevantes: minutos de vídeo por mês, gravações armazenadas, operadores simultâneos.
### 4. Gravação de chamada
Módulo `video_recording`. LiveKit Egress grava para o MinIO ao entrar em `EM_CHAMADA`, se habilitado.
**Aviso reforçado obrigatório:** ao gravar, o visitante vê aviso explícito antes do início, e a versão desse aviso é registrada. Gravar sem avisar é violação direta.
Retenção de 90 dias. Cada gravação soma banda no SFU — ative com quota.
### 5. Retenção e expurgo
Job diário `MEDIA_PURGE`:
1. Seleciona `media_assets` com `expires_at < now()` e `storage_key IS NOT NULL`
2. Apaga o objeto no MinIO
3. `UPDATE media_assets SET storage_key = NULL, purged_at = now()`
4. **Preserva a linha**
O passo 4 é o ponto: fica provado que existiu uma foto e que ela foi eliminada no prazo — exatamente o que se demonstra numa fiscalização.
Prazos configuráveis por tenant, com **teto** definido pela plataforma. Um condomínio querendo guardar foto por 5 anos configuraria um risco que a plataforma não aceita hospedar.
Job diário de limpeza de `idempotency_keys` vencidas.
### 6. Direitos do titular
`POST /admin/lgpd/export` — busca por CPF ou telefone, devolve JSON com todas as visitas, mídias (URLs assinadas) e registros de aviso.
`POST /admin/lgpd/erase` — apaga mídia, anonimiza `visitor_name` para `[REMOVIDO]`, limpa documento e telefone, **preserva a linha da visita** com data, unidade e resultado. Justificativa: obrigação de guarda de registro de acesso para segurança patrimonial. A linha vira estatística, não identificação.
Ambas registram em `audit_log` e devolvem comprovante em PDF — o condomínio precisa provar que atendeu no prazo de 15 dias.
### 7. Versionamento do aviso de privacidade
Editor no admin. Alterar o texto **cria nova versão**; as visitas antigas continuam apontando para a versão que foi realmente exibida. Sem isso, não há como provar o que o visitante leu naquele dia.
### 8. SLOs medidos
Instrumentar as métricas de `01-ARQUITETURA.md` §5.7:
```
portaria.visit.ring_latency QR → push entregue no dispositivo
portaria.visit.answered_ratio atendidas pelo morador / total
portaria.delivery.resolution_latency QR → resolvida
portaria.delivery.abandoned_ratio canceladas ou expiradas
portaria.entry_flow.availability caminho crítico
```
`ring_latency` mede até a **entrega** do push, não até o envio. A diferença entre os dois é justamente onde a falha acontece.
Dashboards e alertas conforme `05-INFRA-DOCKER.md` §6, incluindo os dois alertas de negócio (taxa de atendimento baixa e tokens mortos) que viram tarefa de customer success, não plantão.
### 9. Runbooks
`docs/anexos/runbook-incidente.md` — vazamento, indisponibilidade, perda de dados; com prazos ANPD.
`docs/anexos/runbook-operacao.md` — restore testado, rotação de segredo, invalidação de QR, adição de condomínio.
`docs/anexos/LIA-modelo.md` — modelo de Legitimate Interest Assessment para preenchimento por condomínio.
## Critérios de aceite
- [ ] Dispatcher despacha em paralelo e tolera falha de um canal
- [ ] `WhatsAppChannel` desligado não é invocado; ligado, registra o que enviaria
- [ ] Toggle de módulo tem efeito imediato e fica em `audit_log`
- [ ] Gravação só ocorre com módulo ativo e aviso reforçado exibido
- [ ] `MEDIA_PURGE` apaga o objeto e preserva a linha
- [ ] Exportação LGPD devolve todos os dados de um titular
- [ ] Eliminação anonimiza e preserva a linha da visita
- [ ] Alterar o aviso cria versão; visitas antigas mantêm a original
- [ ] Todos os SLOs visíveis no Grafana com dados reais
- [ ] Alertas disparam em condição simulada
- [ ] Restore de backup testado e cronometrado
## Não faça nesta fase
- Integração real com a Meta (v2)
- Billing automatizado (v2)
- Ativar multi-tenancy