sistema-de-chamados/docs/OPERATIONS.md
esdrasrenan e2dde8510a fix(convex): adicionar logs obrigatorios em cron jobs para evitar shape_inference errors
- Adicionar console.log no inicio de autoEndInactiveSessions (liveChat.ts)
- Adicionar console.log no inicio de cleanupStalePendingPolicies (usbPolicy.ts)
- Documentar problema de shape_inference e solucao em OPERATIONS.md (Secao 11)
- Atualizar .env.example com BETTER_AUTH_SECRET de 32+ caracteres

O shape_inference do Convex self-hosted falha ao unificar arrays vazios
(logLines: []) com arrays de strings (logLines: ["msg"]). Garantindo que
todo cron job produza ao menos um log, evitamos o conflito de tipos.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 00:01:49 -03:00

395 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Operações — Sistema de Chamados (Prod)
Este documento consolida as mudanças recentes, o racional por trás delas e o procedimento de operação/deploy (Web e Convex selfhosted) do ambiente em produção.
## 1) Mudanças Funcionais (Front + Server)
- Empresas (admin)
- “Slug” renomeado para “Apelido” (mensagens e placeholders ajustados).
- Fila padrão ao criar tickets
- Todo novo ticket entra na fila “Chamados”.
- Implementado no backend (fallback) e préseleção na UI.
- Status de tickets e interações
- Nomes/cores atualizados:
- Pendente (cinza), Em andamento (azul), Pausado (amarelo), Resolvido (verde).
- Transições automáticas:
- Iniciar (play) → status “Em andamento”.
- Pausar → status “Pausado”.
- “Encerrar” permanece manual. O dropdown foi substituído por badge + botão “Encerrar”.
- Diálogo de encerramento: botão “Cancelar” adicionado (além de “Limpar mensagem”/“Encerrar ticket”).
- Dashboard — Últimos chamados
- Prioridade: sem responsável primeiro (dos mais antigos para os mais recentes), depois demais chamados.
- Filtros de tickets
- Filtro por “Responsável” (agente/admin) adicionado.
- Salvar filtro como padrão por usuário (localStorage) + “Limpar padrão”.
- Empresas no filtro: lista completa via API admin (não só empresas presentes em tickets).
- Produção: filtro “Responsável” agora é feito no servidor (assigneeId); o front não envia mais parâmetros inválidos.
- Editor de comentários (Tiptap)
- Correção: reativar edição quando um responsável é atribuído (o editor agora reflete mudanças em `disabled` via `setEditable`).
## 2) Convex (SelfHosted) — Ajustes e Motivo
- Problema observado: deploy do Convex falhava no CI por:
- Ausência de `convex.json` (link do projeto) no servidor.
- Uso incorreto de `CONVEX_DEPLOYMENT` junto a `CONVEX_SELF_HOSTED_URL` + `CONVEX_SELF_HOSTED_ADMIN_KEY` (não suportado pelo CLI ao usar selfhosted).
- Divergência de schema (campo `provisioningCode` já existente nos dados, mas ausente no schema do Convex no servidor).
- Medidas aplicadas:
- Atualização do schema do Convex no servidor: inclusão de `provisioningCode?: string` na tabela `companies` (e índice opcional `by_provisioning_code`).
- Criação do link de projeto (`convex.json`) no servidor via wizard do CLI (ver Passo a passo abaixo).
- Ajustes no workflow do GitHub Actions para selfhosted:
- Adicionado passo “Acquire Convex admin key” no job de deploy do Convex.
- Removido `CONVEX_DEPLOYMENT` quando `CONVEX_SELF_HOSTED_URL` + `CONVEX_SELF_HOSTED_ADMIN_KEY` estão definidos.
- Cópia automática do `convex.json` de `/srv/apps/sistema` para o diretório de build temporário.
- Forçar redeploy das funções: tocar arquivos sob `convex/**` para acionar o filtro do job “Deploy Convex functions”.
## 3) CI/CD — Visão Geral
- Pipeline “CI/CD Web + Desktop” (GitHub Actions)
- Job “Detect changes” usa filtros por paths.
- Job “Deploy (VPS Linux)” cuida do Web (Next.js) e stack do Swarm.
- Job “Deploy Convex functions” roda quando há mudanças em `convex/**` ou via `workflow_dispatch`.
- Passos relevantes:
- “Acquire Convex admin key” (via container `sistema_convex_backend`).
- “Bring convex.json from live app if present” (usa o arquivo de link do projeto em `/srv/apps/sistema`).
- “convex env list” e “convex deploy” com `CONVEX_SELF_HOSTED_URL` + `CONVEX_SELF_HOSTED_ADMIN_KEY`.
## 4) Troca de colaborador / reaproveitamento de dispositivo
Quando um computador muda de dono (ex.: João entrega o equipamento antigo para Maria e recebe uma dispositivo nova), siga este checklist para manter o inventário consistente:
1. **No painel (Admin → Dispositivos)**
- Abra os detalhes da dispositivo que será reaproveitada (ex.: a “amarela” que passará da TI/João para a Maria).
- Clique em **Resetar agente**. Isso revoga todos os tokens gerados para aquele equipamento; ele precisará ser reprovisionado antes de voltar a reportar dados.
- Abra **Ajustar acesso** e altere o e-mail para o do novo usuário (Maria). Assim, quando o agente se registrar novamente, o painel já mostrará a responsável correta.
2. **Na dispositivo física que ficará com o novo colaborador**
- Desinstale o desktop agent (Painel de Controle → remover programas).
- Instale novamente o desktop agent. Use o mesmo **código da empresa/tenant** e informe o **e-mail do novo usuário** (Maria). O backend emite um token novo e reaproveita o registro da dispositivo, mantendo o histórico.
3. **Dispositivo nova para o colaborador antigo**
- Instale o desktop agent do zero na dispositivo que o João vai usar (ex.: a “azul”). Utilize o mesmo código da empresa e o e-mail do João.
- A dispositivo azul aparecerá como um **novo registro** no painel (inventário/tickets começarão do zero). Renomeie/associe conforme necessário.
4. **Verificação final**
- A dispositivo antiga (amarela) continua listada, agora vinculada à Maria, com seus tickets históricos.
- A dispositivo nova (azul) aparece como um segundo registro para o João. Ajuste hostname/descrição para facilitar a identificação.
> Não é necessário excluir registros. Cada dispositivo mantém seu histórico; o reset garante apenas que o token antigo não volte a sobrescrever dados quando o hardware mudar de mãos.
- Importante: não usar `CONVEX_DEPLOYMENT` em conjunto com URL + ADMIN_KEY.
- Como forçar o deploy do Convex
- Faça uma alteração mínima em `convex/**` (ex.: comentário em `convex/tickets.ts`) ou rode o workflow em “Run workflow” (workflow_dispatch).
## 4) Convex — Provisionamento inicial (selfhosted)
Executar apenas 1x na VPS para criar o link do projeto (`convex.json`):
```bash
cd /srv/apps/sistema
export CONVEX_SELF_HOSTED_URL="https://convex.esdrasrenan.com.br"
CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}')
export CONVEX_SELF_HOSTED_ADMIN_KEY="$(docker exec -i "$CID" /bin/sh -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1)"
npx convex dev --once --configure=new
# Siga o wizard (self-hosted) e vincule/crie o projeto/deployment (ex.: "sistema" / "default").
# Isso gera /srv/apps/sistema/convex.json
```
Depois disso, o job “Deploy Convex functions” funciona em modo não interativo.
## 5) VPS — Acesso e Serviços
- Acesso
- Host: `31.220.78.20`
- Usuário: `root`
- Chave SSH (repo raiz): `./codex_ed25519` (Atenção: manter permissões 600)
- Exemplo: `ssh -i ./codex_ed25519 root@31.220.78.20`
- Opcional (endurecimento): desabilitar login por senha após validar a chave.
- Diretórios principais
- Código do app: `/srv/apps/sistema`
- Arquivo do projeto Convex: `/srv/apps/sistema/convex.json`
- Stack do Swarm: `stack.yml` (no repositório; aplicado no servidor via CI).
- Serviços (Docker Swarm + Traefik)
- Web (Next.js): serviço `sistema_web`, exposto em `tickets.esdrasrenan.com.br`.
- Convex backend: serviço `sistema_convex_backend`, exposto em `convex.esdrasrenan.com.br`.
- Convex dashboard: `convex-admin.esdrasrenan.com.br`.
- Comandos úteis:
- `docker service ls`
- `docker service ps sistema_web`
- `docker service update --force sistema_web` (reiniciar)
- `docker service update --force sistema_convex_backend` (reiniciar Convex)
- Convex admin key (diagnóstico)
- `docker exec -i $(docker ps | awk '/sistema_convex_backend/{print $1; exit}') /bin/sh -lc './generate_admin_key.sh'`
- Usada no CI para `convex env list` e `convex deploy`.
## 6) Notas de Segurança
- A chave privada `codex_ed25519` está na raiz do repo (ambiente atual). Em produção, recomendase:
- Remover a chave do repositório ou armazenála em Secrets/Deploy keys do provedor.
- Desabilitar login por senha no SSH (apenas chave).
- Manter permissões: `chmod 600 ./codex_ed25519`.
## 7) Testes, Build e Lint
- Local
- `bun run build:bun` (Next + typecheck)
- `bun run lint`
- `bun test`
- CI garante build, lint e testes antes do deploy.
## 8) Troubleshooting Rápido
- “No CONVEX_DEPLOYMENT set” durante o deploy do Convex
- Certifiquese de que `/srv/apps/sistema/convex.json` existe (rodar wizard `npx convex dev --once --configure=new`).
- Não usar `CONVEX_DEPLOYMENT` com `CONVEX_SELF_HOSTED_URL` + `CONVEX_SELF_HOSTED_ADMIN_KEY`.
- Login quebrando com erro de `better_sqlite3` (NODE_MODULE_VERSION)
- Sintoma: HTTP 500 em `/api/auth/sign-in/email` e logs do `sistema_web` com “Could not locate the bindings file” ou “compiled against NODE_MODULE_VERSION 115”.
- Causa: o `better-sqlite3` precisa ser recompilado para a versão de Node em uso (rodamos Node 22 dentro do container).
- Correção (uma vez, após trocar versão de Node/Bun ou limpar `node_modules`):
```
docker run --rm -v /apps/sistema:/app sistema_web:node22-bun \
bash -lc "cd /app && npm rebuild better-sqlite3"
docker service update --force sistema_web
```
Isso grava o binário em `/apps/sistema/node_modules/.bun/better-sqlite3@11.10.0/...` (path montado pelo serviço).
- Flags no serviço (stack.yml):
- `SKIP_APT_BOOTSTRAP=true` evita apt-get no boot (imagem já tem toolchain).
- `SKIP_SQLITE_REBUILD=true` deixa o boot rápido; deixe como `true` depois de recompilar manualmente como acima.
- “Schema validation failed” (campo extra `provisioningCode`)
- Atualize o schema do Convex no servidor para incluir `provisioningCode?: string` em `companies`.
- Refaça o deploy.
- Filtro “Responsável” não funciona
- Front envia `assigneeId` e o backend Convex deve aceitar esse parâmetro (função `tickets.list`).
- Se necessário, forçar redeploy das funções (`convex/**`).
- Admin ▸ Dispositivos travado em skeleton infinito
- Abra o DevTools (console) e filtre por `admin-machine-details`. Se o log mostrar `machineId: undefined`, o componente não recebeu o parâmetro da rota.
- Verifique se o `page.tsx` está passando `params.id` corretamente ou se o componente client-side usa `useParams()` / `machineId` opcional.
- Deploys antigos antes de `fix(machines): derive machine id from router params` precisam desse patch; sem ele o fallback nunca dispara e o skeleton permanece.
- Export do Convex em loop (`application::exports::worker`)
- Sintoma: WebSocket `1006`, logs com `Caught error ... shape_inference`, `Export <id> beginning...` repetindo.
- Solução completa documentada em `docs/convex-export-worker-loop.md` (backup do SQLite, limpeza dos registros defeituosos e rerun do export).
---
Última atualização: sincronizado após o deploy bemsucedido do Convex e do Front (20/10/2025).
## 9) Admin ▸ Usuários e Dispositivos — Unificação e UX
Resumo das mudanças aplicadas no painel administrativo para simplificar “Usuários” e “Agentes de dispositivo” e melhorar o filtro em Dispositivos:
- Unificação de “Usuários” e “Agentes de dispositivo”
- Antes: abas separadas “Usuários” (pessoas) e “Agentes de dispositivo”.
- Agora: uma só aba “Usuários” com filtro de tipo (Todos | Pessoas | Dispositivos).
- Onde: `src/components/admin/admin-users-manager.tsx:923`, aba `value="users"` em `:1147`.
- Motivo: evitar confusão entre “usuário” e “agente”; agentes são um tipo especial de usuário (role=machine). A unificação torna “Convites e Acessos” mais direta.
- Dispositivos ▸ Filtro por Empresa com busca e remoção do filtro de SO
- Adicionado dropdown de “Empresa” com busca (Popover + Input) e removido o filtro por Sistema Operacional.
- Onde: `src/components/admin/devices/admin-devices-overview.tsx:1038` e `:1084`.
- Motivo: fluxo real usa empresas com mais frequência; filtro por SO era menos útil agora.
- Windows ▸ Rótulo do sistema sem duplicidade do “major”
- Exemplo: “Windows 11 Pro (26100)” em vez de “Windows 11 Pro 11 (26100)”.
- Onde: `src/components/admin/devices/admin-devices-overview.tsx` (função `formatOsVersionDisplay`).
- Motivo: legibilidade e padronização em chips/cartões.
- Vínculos visuais entre dispositivos e pessoas
- Cards de dispositivos mostram “Usuário vinculado” quando disponível (assignment/metadata): `src/components/admin/devices/admin-devices-overview.tsx:3198`.
- Editor de usuário exibe “Dispositivos vinculadas” (derivado de assign/metadata): `src/components/admin/admin-users-manager.tsx` (seção “Dispositivos vinculadas” no sheet de edição).
- Observação: por ora é leitura; ajustes detalhados de vínculo permanecem em Admin ▸ Dispositivos.
### Identidade, email e histórico (reinstalação)
- Identificador imutável: o histórico (tickets, eventos) referencia o `userId` (imutável). O email é um atributo mutável.
- Reinstalação do desktop para o mesmo colaborador: reutilize a mesma conta de usuário (mesmo `userId`); se o email mudou, atualize o email dessa conta no painel. O histórico permanece, pois o `userId` não muda.
- Novo email como nova conta: se criar um usuário novo (novo `userId`), será considerado um colaborador distinto e não herdará o histórico.
- Caso precise migrar histórico entre contas diferentes (merge), recomendamos endpoint/rotina de “fusão de contas” (remapear `userId` antigo → novo). Não é necessário para a troca de email da mesma conta.
### Vínculos múltiplos de usuários por dispositivo (Fase 2)
- Estrutura (Convex):
- `machines.linkedUserIds: Id<"users">[]` — lista de vínculos adicionais além do `assignedUserId` (principal).
- Mutations: `machines.linkUser(machineId, email)`, `machines.unlinkUser(machineId, userId)`.
- APIs admin: `POST /api/admin/devices/links` (body: `{ machineId, email }`), `DELETE /api/admin/devices/links?machineId=..&userId=..`.
- UI:
- Detalhes da dispositivo mostram “Usuários vinculados” com remoção por item e campo para adicionar por email.
- Editor de usuário mostra “Dispositivos vinculadas” consolidando assignment, metadata e `linkedUserIds`.
- Racional: permitir que uma dispositivo tenha mais de um colaborador/gestor associado, mantendo um “principal” (persona) para políticas e contexto.
### Onde editar
- Usuários (pessoas): editar nome, email, papel, tenant e empresa; redefinir senha pelo painel. Arquivo: `src/components/admin/admin-users-manager.tsx`.
- Agentes (dispositivos): provisionamento automático; edição detalhada/vínculo principal em Admin ▸ Dispositivos. Arquivo: `src/components/admin/devices/admin-devices-overview.tsx`.
> Observação operacional: mantivemos o provisionamento de dispositivos inalterado (token/email técnico), e o acesso web segue apenas para pessoas. A unificação é de UX/gestão.
## 10) Convex Self-Hosted — Otimizacao de Memoria (OOM)
### Problema
O Convex self-hosted carrega **todas as versoes de todos os documentos** em memoria (SQLite in-memory). Com heartbeats de dispositivos a cada 30s, cada `patch()` criava uma nova versao do documento `machines`, causando:
- Acumulo exponencial: ~3500 versoes para ~65 maquinas
- Uso de memoria crescente: 8-10 GiB para poucos dispositivos
- Crashes por OOM e desconexoes WebSocket (codigo 1006)
### Solucao Implementada (2025-12-09)
**1. Separacao de heartbeats em tabela dedicada**
Nova tabela `machineHeartbeats` armazena apenas `lastHeartbeatAt`:
```typescript
// convex/schema.ts
machineHeartbeats: defineTable({
machineId: v.id("machines"),
lastHeartbeatAt: v.number(),
}).index("by_machine", ["machineId"]),
```
**2. Heartbeat inteligente**
A funcao `heartbeat` agora:
- SEMPRE atualiza `machineHeartbeats` (documento pequeno, upsert)
- SO atualiza `machines` quando ha mudancas reais (hostname, OS, metadata, status)
```typescript
// Verificar se ha mudancas reais nos dados
const hasMetadataChanges = Object.keys(metadataPatch).length > 0
const hasHostnameChange = args.hostname && args.hostname !== machine.hostname
const needsMachineUpdate = hasMetadataChanges || hasHostnameChange || ...
// So atualizar machines se houver mudancas reais
if (needsMachineUpdate) {
await ctx.db.patch(machine._id, { ...patch }) // SEM lastHeartbeatAt
}
```
**3. Filtragem de campos volumetricos**
`INVENTORY_BLOCKLIST` remove campos que mudam frequentemente ou sao muito grandes:
- `software` (lista de programas instalados)
- `extended` (dados estendidos)
Hardware (CPU, memoria, placa-mae) **permanece visivel** em `/admin/devices`.
### Resultado
| Metrica | Antes | Depois |
|---------|-------|--------|
| Uso de memoria | 8-10 GiB | 4-5 GiB |
| Versoes por maquina | ~50/dia | ~2-3/dia |
| Percentual RAM (20 GiB) | 40-50% | 20-25% |
### Monitoramento
```bash
# Verificar uso de memoria
docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}' | grep convex
# Verificar OOM kills recentes
journalctl -k | grep -i 'oom\|killed' | tail -20
# Restart do servico se necessario
docker service update --force sistema_convex_backend
```
### Limites de Memoria (Docker Swarm)
Configurado em `stack.yml`:
```yaml
services:
convex_backend:
deploy:
resources:
limits:
memory: 20G
reservations:
memory: 8G
```
Para alterar via CLI:
```bash
docker service update --limit-memory 20G --reserve-memory 8G sistema_convex_backend
```
## 11) Convex Self-Hosted — Erros de shape_inference (WebSocket 1006)
### Problema
O Convex self-hosted usa um sistema de **shape inference** para otimizar queries e exports. Quando documentos da mesma tabela tem campos com tipos incompativeis, o sistema falha ao unificar os "shapes".
**Sintoma observado:**
- WebSocket desconectando com codigo `1006` (abnormal closure)
- Logs com erro `shape_inference` repetidos
- Export em loop: `Export <id> beginning...` repetindo indefinidamente
**Causa raiz:**
A tabela `_scheduled_job_logs` acumulou milhares de registros de cron jobs. Alguns tinham:
- `logLines: []` (array vazio)
- `logLines: ["mensagem de log"]` (array com strings)
O shape inference nao consegue unificar `Array<never>` com `Array<string>`, causando falha continua.
### Solucao Implementada (2025-12-09)
**1. Garantir que cron jobs sempre produzam logs**
Adicionamos `console.log()` obrigatorio no inicio de cada handler de cron:
```typescript
// convex/liveChat.ts - autoEndInactiveSessions
handler: async (ctx) => {
console.log("cron: autoEndInactiveSessions iniciado")
// ... resto do codigo
}
// convex/usbPolicy.ts - cleanupStalePendingPolicies
handler: async (ctx, args) => {
console.log("cron: cleanupStalePendingPolicies iniciado")
// ... resto do codigo
}
```
**2. Por que funciona**
Com o log obrigatorio, **todos** os registros de `_scheduled_job_logs` terao:
- `logLines: ["cron: <nome> iniciado", ...]`
Isso garante consistencia de tipos (sempre `Array<string>`), evitando o conflito de shapes.
### Arquivos Modificados
- `convex/liveChat.ts:751` — log no inicio de `autoEndInactiveSessions`
- `convex/usbPolicy.ts:313` — log no inicio de `cleanupStalePendingPolicies`
### Monitoramento
```bash
# Verificar se ha erros de shape_inference nos logs
ssh root@154.12.253.40 "docker service logs sistema_convex_backend 2>&1 | grep -i 'shape_inference' | tail -10"
# Verificar status dos cron jobs no dashboard
# Acesse: convex-admin.esdrasrenan.com.br > Scheduled Jobs
```
### Notas
- Este e um bug interno do Convex self-hosted que pode ser corrigido em versoes futuras
- A solucao de adicionar logs obrigatorios e um workaround que nao afeta performance
- Se novos cron jobs forem adicionados, **sempre incluir um console.log no inicio**