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
| # | Achado | Severidade | Evidência (antes) | Correção | Teste |
|---|---|---|---|---|---|
| 1 | Os 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 algum | Novo 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.mjs — SponsorshipExpiryScheduler sweep cancels overdue pending payments and expires overdue active windows; services/api/test/finance.test.mjs — WalletExpiryScheduler sweep expires due lots across every wallet; scripts/validation/check-observability-invariants.mjs (job-sweep-missing-structured-logging) |
| 2 | Os 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ência | Novo 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) |
| 3 | Os 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.3 | Có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/healthjá é público, responde comrequestId/timestampno 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:
TechnicalLoggingInterceptorjá logaservice,requestId,route,method,statusCode,durationMs,errorCodeem 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). requestIdjá chega ao cliente em toda resposta, sucesso ou erro (ApiResponse.meta.requestId, construído tanto pelo backend real quanto porApiClient#technicalErrordo 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.tsnovo,main.ts,report-client-error.tsnovo) 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.mdjá 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 comSPEC-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/rejectde denúncias e solicitações de suporte (SPEC-DOMAIN-REPORT-001). A máquina de estados completa já existe como função pura empackages/contracts/src/report-support/status.ts, mas nenhum endpoint, tela ou script a invoca — todo protocolo real fica emreceivedpara 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 emrunbooks/support-operations.md(novo) e registrado como R-014 emdocs/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
resolveViewercom 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 emsponsors.test.mjs/finance.test.mjs(2),report-client-error.test.mjs(3),error-boundary.test.tsem web e mobile (2).pnpm run repository:validate— inclui o novopnpm 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.