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

20 KiB
Raw Blame History

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):

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:

// 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)
// 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

# 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:

services:
  convex_backend:
    deploy:
      resources:
        limits:
          memory: 20G
        reservations:
          memory: 8G

Para alterar via CLI:

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:

// 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

# 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