Pular para o conteúdo principal

Status: Approved · v1 · SPEC-DELIVERY-OBS-JOBS-AUDIT-001

depends_on: SPEC-ARCH-OBS-001

used_by: IS-MVP-15.4

Auditoria de observabilidade, jobs e preparação operacional

O que foi auditado, o que foi corrigido nesta pack e o que fica registrado como risco conhecido para uma pack futura (EP-MVP-15.4).

Método

Leitura direta do código real de services/api/src (logging técnico, health, os três @Cron jobs) e de apps/web/apps/mobile (error boundaries), não apenas das specs — mesmo método das duas auditorias anteriores (security_privacy_audit.md, performance_accessibility_audit.md). Cada achado foi confirmado lendo o arquivo:linha real, depois corrigido e coberto por um teste automatizado antes de ser classificado como corrigido. NEXT_STEPS.md já pré-registrava um dos três achados como o trabalho esperado desta pack (linha 31, referente ao sweep de auto-validação de partidas) — confirmando que a leitura encontrou o mesmo gap que o handoff anterior havia previsto, não uma reinterpretação nova.

Achados corrigidos nesta pack

#AchadoSeveridadeEvidência (antes)CorreçãoTeste
1Os três @Cron jobs (MatchesAutoValidationScheduler, SponsorshipExpiryScheduler, WalletExpiryScheduler) rodavam sem nenhum log — nem sucesso, nem falha, nem contagem de itens processados. SPEC-ARCH-OBS-001 exige "Alertas iniciais: ... falha persistente em webhook/jobs críticos", mas não existia nenhum sinal observável para basear esse alerta: um job que parasse de rodar, ou lançasse exceção em toda execução, era indistinguível de um job sem trabalho a fazer. Já registrado como o trabalho esperado desta pack em NEXT_STEPS.md linha 31.HIGH (a própria pré-condição do critério de alerta da spec estava ausente)matches-auto-validation.scheduler.ts#handleAutoValidationSweep (e os outros dois) chamavam o application service e descartavam o retorno, sem Logger algumNovo helper services/api/src/common/job-execution.logger.ts#runJobSweep, usado pelos três schedulers: emite uma linha JSON estruturada por execução (event: 'job.sweep_completed' com durationMs + contagens específicas do job, ou event: 'job.sweep_failed' com errorMessage), nunca deixa uma exceção derrubar o processo ou cancelar a próxima execução agendada. As três application services (MatchesApplicationService#runAutoValidationSweep, SponsorshipApplicationService#runExpirySweep, WalletApplicationService#runExpirySweep) foram ajustadas para devolver contagens (validatedMatchIds.length/{cancelledCount, expiredCount}/{expiredLotsCount}) em vez de void — mudança apenas de tipo de retorno interno, nenhum contrato/DTO/endpoint HTTP mudou.services/api/test/job-execution-logger.test.mjs (sucesso e falha do wrapper); services/api/test/sponsors.test.mjsSponsorshipExpiryScheduler sweep cancels overdue pending payments and expires overdue active windows; services/api/test/finance.test.mjsWalletExpiryScheduler sweep expires due lots across every wallet; scripts/validation/check-observability-invariants.mjs (job-sweep-missing-structured-logging)
2Os error boundaries raiz de web (apps/web/app/error.tsx) e mobile (apps/mobile/app/_layout.tsx) recebem o error capturado (o mobile já o recebia via prop; o web recebia mas nunca o declarava) e o descartavam por completo — nenhum console.error, nenhum log, nenhuma captura de qualquer tipo. SPEC-ARCH-OBS-001 exige "Erros inesperados devem ser capturáveis e apresentar fallback profissional"; a segunda metade (fallback) já existia, a primeira (capturável) não existia em nenhum grau.HIGH (metade do critério de aceite da spec inexistente em produção)error.tsx desestruturava só { reset }; _layout.tsx's ErrorBoundary desestruturava só { retry } — grep por console.error/console.warn em todo apps/web/src+apps/web/app+apps/mobile/src+apps/mobile/app não retornava nenhuma ocorrênciaNovo reportClientError(platform, error, digest?) em packages/shared/src/observability/report-client-error.ts — um log JSON estruturado (service, level, event: 'client.unhandled_error', message, digest?), mesmo vocabulário que TechnicalLoggingInterceptor já usa no backend. console.error é o próprio adapter do MVP (SPEC-ARCH-OBS-001 "Sentry/serviço externo é adapter configurável, não obrigatório localmente") — plugar Sentry depois substitui o corpo desta função, não os dois error boundaries. Ambos os boundaries agora chamam essa função (useEffect no mount do erro, mesmo padrão que a documentação do Next.js recomenda para error.tsx).packages/shared/test/report-client-error.test.mjs (3 casos: Error normal, com digest, valor não-Error); apps/web/test/error-boundary.test.ts/apps/mobile/test/error-boundary.test.ts (leitura de fonte confirmando a chamada); scripts/validation/check-observability-invariants.mjs (error-boundary-missing-capture)
3Os dois error boundaries mostravam o texto "O bootstrap não pôde continuar." para qualquer erro não tratado em qualquer lugar do app — não só uma falha de primeiro boot (o único cenário para o qual essa cópia fazia sentido quando foi escrita, na Sprint Zero). Um erro de render dentro de /management ou /feed, por exemplo, mostraria uma mensagem sobre "bootstrap", confusa/não profissional para o usuário e sem relação real com a causa.MEDIUM (UX/microcopy, não um bug funcional)error.tsx/_layout.tsx, cópia fixa desde EP-ZERO-00.3Cópia generalizada para "Algo deu errado." + instrução de tentar novamente/voltar, apropriada para qualquer erro de render, mantendo o mesmo tratamento visual (bootstrap-shell/bootstrap-content, tokens já corretos).check-observability-invariants.mjs (error-boundary-misleading-copy)

Confirmado íntegro (sem correção necessária)

Verificado por leitura direta do código, não apenas pela spec:

  • Health endpoint: GET /api/v1/health já é público, responde com requestId/timestamp no envelope, nunca vaza stack (services/api/test/health.test.mjs, 3 testes pré-existentes cobrindo isso). Atende "Healthcheck pronto para deploy" e "RequestId em logs e responses" da spec sem qualquer mudança nesta pack.
  • Logs técnicos de request: TechnicalLoggingInterceptor já loga service, requestId, route, method, statusCode, durationMs, errorCode em JSON estruturado, por request — satisfaz "Métricas mínimas: request count/error/duration" da spec sem exigir um endpoint de métricas dedicado (a própria spec descreve isso como o mecanismo, não uma agregação separada; "sem plataforma enterprise prematura" é a tagline explícita da spec).
  • requestId já chega ao cliente em toda resposta, sucesso ou erro (ApiResponse.meta.requestId, construído tanto pelo backend real quanto por ApiClient#technicalError do lado do cliente) — a spec "Propagar requestId em falhas" já estava atendida para toda falha de API antes desta pack; o achado #2 acima é especificamente sobre falhas de render no cliente, que não têm um request associado.
  • Nenhum segredo nos logs: os únicos arquivos que logam algo (technical-logging.interceptor.ts, job-execution.logger.ts novo, main.ts, report-client-error.ts novo) nunca incluem senha, token, corpo de request/response, Pix code ou documento — confirmado por leitura direta de cada um; os novos logs de job só carregam contagens numéricas e nome do job, nunca um registro de domínio inteiro.
  • "Eventos de produto" (SPEC-ARCH-OBS-001 "eventos de produto definidos na spec de UX"): nenhuma spec de UX (docs/03_ux/**) define uma lista/taxonomia de eventos de analytics a instrumentar — as ocorrências de "evento" nesses documentos referem-se a eventos de domínio (gol, cartão, substituição), não a analytics de produto. O mecanismo real que a spec distingue de log técnico — "Audit log é dado de produto persistente e consultável por permission" — já existe extensivamente (audit trail de gestão de time/torcida, ledger de carteira, timeline de reports/support requests, histórico de retificação de partida), todos com testes próprios de packs anteriores. Não há uma lacuna concreta a fechar aqui sem inventar uma taxonomia hipotética.
  • Runbooks/alertas já qualitativos por design: runbooks/incident-response.md já definia os três alertas iniciais da spec (API indisponível, taxa de erro elevada, falha de job/webhook) de forma qualitativa, sem número artificial — consistente com SPEC-ARCH-PERF-001 ("Metas numéricas são definidas após baseline da primeira vertical") e com a instrução do próprio IS de não inferir decisões ambíguas. Esta pack só atualizou a seção de job/webhook para descrever o vocabulário de log novo (ver achado #1), sem inventar um limiar numérico.

Registrado como risco, não corrigido nesta pack

  • Nenhum caminho de código executa triage/resolve/reject de denúncias e solicitações de suporte (SPEC-DOMAIN-REPORT-001). A máquina de estados completa já existe como função pura em packages/contracts/src/report-support/status.ts, mas nenhum endpoint, tela ou script a invoca — todo protocolo real fica em received para sempre dentro deste sistema, a menos que o próprio solicitante cancele. O domínio já documenta o painel admin como "Pós-MVP", então isso não é uma regressão desta pack nem um bug a corrigir silenciosamente — construir esse endpoint seria nova superfície de produto, fora do "no new product feature" desta auditoria. Documentado honestamente em runbooks/support-operations.md (novo) e registrado como R-014 em docs/07_delivery/risk_register.md, com a decisão pendente explícita (endpoint/CLI interno mínimo vs. aceitar apenas demonstração até o painel pós-MVP) marcada como não inferível por esta pack.
  • Riscos herdados de packs anteriores (R-011 camada legada resolveViewer com tokens fixos; R-012 segredo de webhook Pix placeholder; R-013 CLS residual de /search) seguem registrados nos handoffs correspondentes, sem alteração por este pack — nenhum é observabilidade/jobs.

Comandos e resultados

Todos executados com CI=true NODE_ENV=production {NEXT_PUBLIC,EXPO_PUBLIC}_USE_MOCKS=false {NEXT_PUBLIC,EXPO_PUBLIC}_API_BASE_URL=https://api-staging.raizfc.com.br (mesmas variáveis do gate de CI):

  • pnpm run typecheck — 11/11 tasks, verde.
  • pnpm run lint — boundaries + ESLint + Prettier, verde.
  • pnpm run test — verde, sem regressão nos módulos não tocados; novos: job-execution-logger.test.mjs (2), sweep de patrocínio e carteira em sponsors.test.mjs/finance.test.mjs (2), report-client-error.test.mjs (3), error-boundary.test.ts em web e mobile (2).
  • pnpm run repository:validate — inclui o novo pnpm run observability:invariants (scripts/validation/check-observability-invariants.mjs), 3 guardas de regressão verdes.
  • pnpm run build — 7/7 tasks.
  • pnpm run validate:contract-parity — verde (nenhum contrato/DTO/endpoint HTTP mudou; a mudança de tipo de retorno dos métodos de sweep é interna às application services, nunca exposta via HTTP).

Não coberto por esta auditoria

  • Construção de um painel/endpoint de moderação real — decisão de produto pendente, registrada como R-014, fora do escopo de "no new product feature".
  • Persistência real (banco) para jobs/audit log/protocolos de suporte — todo o MVP já opera em memória por design (runbooks/deploy.md "Limitações honestas"); não é uma lacuna de observabilidade específica desta pack.
  • Integração real de Sentry/serviço de erro externo — a spec explicitamente não obriga isso localmente; reportClientError é o ponto de adaptação já preparado para quando essa decisão for tomada.
  • Limiares numéricos de alerta (ex.: "taxa de erro > X% em Y minutos") — a spec de performance explicitamente adia metas numéricas para depois de um baseline real de produção; inventar um número agora seria infraestrutura prematura sobre um risco hipotético.