Skip to content

Resiliência e Observabilidade

Padrões obrigatórios de infraestrutura para resiliência, observabilidade e escalabilidade da API.


1. Visão Geral

┌─────────────────────────────────────────────────────────────────────────┐
│                    ARQUITETURA DE RESILIÊNCIA                           │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────┐    ┌──────────────┐    ┌───────────┐    ┌──────────────┐  │
│  │ Request │───►│ Correlation  │───►│ Controller│───►│   Service    │  │
│  │         │    │ ID Middleware │    │           │    │              │  │
│  └─────────┘    └──────────────┘    └───────────┘    └──────┬───────┘  │
│                                                             │          │
│                        ┌────────────────────────────────────┤          │
│                        │                                    │          │
│                        ▼                                    ▼          │
│              ┌──────────────────┐              ┌────────────────────┐  │
│              │  Circuit Breaker │              │    BullMQ Queue    │  │
│              │  + Retry + Cache │              │  + DLQ + Workers   │  │
│              └────────┬─────────┘              └────────┬───────────┘  │
│                       │                                 │              │
│                       ▼                                 ▼              │
│              ┌──────────────────┐              ┌────────────────────┐  │
│              │  External APIs   │              │  Async Processors  │  │
│              │  (NFSe, CNPJA,   │              │  (PDF, Email,      │  │
│              │   OAuth, Twilio) │              │   NFSe, Cron)      │  │
│              └──────────────────┘              └────────────────────┘  │
│                                                                         │
│  ┌──────────────────────────────────────────────────────────────────┐   │
│  │                    OBSERVABILIDADE                                │   │
│  │  Pino (JSON) → CloudWatch Logs                                   │   │
│  │  OpenTelemetry → Traces + Metrics                                │   │
│  │  Sentry → Errors + Profiling                                     │   │
│  │  Correlation ID → Rastreamento end-to-end                        │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

2. Stack de Infraestrutura

ComponenteTecnologiaFinalidade
Cache / Lock / Queue BrokerRedis 7+Cache distribuído, locks, backend do BullMQ
FilasBullMQProcessamento assíncrono com retry e DLQ
LogsPino + nestjs-pinoLogs estruturados em JSON
TracingOpenTelemetry SDKTraces distribuídos e métricas
ErrorsSentry (existente)Captura de erros e profiling
Circuit BreakercockatielCircuit breaker, retry, timeout
Cache HTTP@nestjs/cache-manager + RedisCache em endpoints de leitura
Health Checks@nestjs/terminusHealth checks profundos

3. Correlation ID

Toda requisição deve ser rastreável end-to-end através de um identificador único.

3.1 Fluxo

┌──────────┐    ┌────────────────────┐    ┌──────────┐    ┌──────────┐
│  Client  │───►│ CorrelationId      │───►│ Service  │───►│ External │
│          │    │ Middleware          │    │          │    │ API Call │
│          │    │                    │    │          │    │          │
│          │    │ Gera UUID se não   │    │ Acessa   │    │ Recebe   │
│          │    │ vier no header     │    │ via ALS  │    │ header   │
└──────────┘    └────────────────────┘    └──────────┘    └──────────┘
                       │                       │
                       ▼                       ▼
                 ┌───────────┐          ┌───────────┐
                 │ Response  │          │   Logs    │
                 │ Header    │          │ (Pino)    │
                 │ X-Corr-ID │          │ corrId    │
                 └───────────┘          └───────────┘

3.2 Regras

  • Header: X-Correlation-ID
  • Se o client enviar, usar o valor recebido
  • Se não enviar, gerar UUID v4 automaticamente
  • Armazenar via AsyncLocalStorage (Node.js nativo)
  • Incluir em todos os logs automaticamente via Pino
  • Propagar para chamadas HTTP externas (NFSe, CNPJA, OAuth)
  • Propagar para jobs de fila (metadata do job BullMQ)
  • Retornar no header da response
  • Cron jobs geram correlation ID próprio por execução

3.3 Estrutura

src/
├── common/
│   ├── middleware/
│   │   └── correlation-id.middleware.ts
│   └── context/
│       └── correlation.context.ts        # AsyncLocalStorage wrapper

4. Logs Centralizados

Logs estruturados em JSON com contexto automático.

4.1 Configuração

PropriedadeValor
Bibliotecanestjs-pino (wrapper do Pino)
FormatoJSON estruturado
Nível devdebug
Nível prodwarn
Destinostdout → CloudWatch Logs
RedaçãoCPF, CNPJ, tokens, senhas

4.2 Campos Obrigatórios em Todo Log

CampoOrigemExemplo
correlationIdAsyncLocalStorage"a1b2c3d4-..."
userIdJWT payload"user-uuid"
companyIdHeader x-company-id"company-uuid"
moduleLogger name"InvoicesService"
levelPino level"info"
timestampAutomático1711670400000
msgMensagem"Invoice created"

4.3 Dados Sensíveis (Redação Automática)

Pino redact paths obrigatórios:

redact:
  - "req.headers.authorization"
  - "req.headers.cookie"
  - "*.password"
  - "*.certificatePassword"
  - "*.token"
  - "*.accessToken"
  - "*.refreshToken"
  - "*.document"           # CPF/CNPJ
  - "*.pfxBase64"

4.4 Estrutura

src/
├── config/
│   └── logger.config.ts              # Configuração do Pino

5. Métricas e Tracing

OpenTelemetry como padrão para traces distribuídos e métricas técnicas.

5.1 Traces

PropriedadeValor
SDK@opentelemetry/sdk-node
PropagaçãoW3C TraceContext
ExportadorOTLP (compatível com CloudWatch, Jaeger, etc.)
Auto-instrumentaçãoHTTP, Prisma, BullMQ
Sample rate prod10% (ajustável)

5.2 Métricas Técnicas

MétricaTipoLabels
http_request_duration_secondsHistogrammethod, route, status
http_requests_totalCountermethod, route, status
db_query_duration_secondsHistogramoperation, model
queue_job_duration_secondsHistogramqueue, status
queue_depthGaugequeue
queue_failed_totalCounterqueue
circuit_breaker_stateGaugeservice (open/closed/half)
cache_hit_totalCounterkey_prefix
cache_miss_totalCounterkey_prefix
external_api_duration_secondsHistogramservice, method

5.3 Métricas de Negócio

MétricaTipoLabels
invoices_emitted_totalCountercompanyId, status
contracts_created_totalCountercompanyId
pdfs_generated_totalCounterstatus (success/error)
compliance_notifications_totalCountertype
onboarding_completions_totalCountercompanyId

5.4 Alarmes Obrigatórios

AlarmeCondiçãoAção
Error Rate Altoerror rate > 5% por 5minAlerta imediato
Latência Altap99 > 3s por 5minAlerta
DLQ AcumulandoDLQ depth > 50 por 10minAlerta imediato
Circuit Opencircuit_breaker_state = openAlerta imediato
DB Pool Esgotandoconnections > 80% poolAlerta

5.5 Estrutura

src/
├── config/
│   └── telemetry.config.ts
├── common/
│   └── metrics/
│       └── metrics.service.ts           # Custom metrics registry

6. Circuit Breaker e Timeout

Todas as chamadas a serviços externos devem ter circuit breaker, timeout e retry.

6.1 Serviços Externos e Suas Políticas

ServiçoTimeoutRetriesBackoffCircuit Breaker
NFSe API30s3exponential (1s, 2s, 4s)5 falhas → open 60s
CNPJA (company lookup)10s2exponential (500ms, 1s)3 falhas → open 30s
OAuth Providers10s2exponential (500ms, 1s)5 falhas → open 60s
AWS SES (email)15s3exponential (1s, 2s, 4s)5 falhas → open 60s
Twilio (SMS)10s2exponential (1s, 2s)3 falhas → open 30s
AWS S3 (storage)30s3exponential (1s, 2s, 4s)5 falhas → open 60s
Puppeteer (PDF)60s13 falhas → open 120s
ui-avatars.com5s13 falhas → open 30s

6.2 Padrão de Implementação com cockatiel

┌──────────┐    ┌─────────┐    ┌───────────────┐    ┌──────────┐
│ Service  │───►│ Timeout │───►│ Circuit       │───►│ External │
│          │    │ Policy  │    │ Breaker       │    │ API      │
│          │    │         │    │               │    │          │
│          │    │         │    │ ┌───────────┐ │    │          │
│          │    │         │    │ │Retry+Back │ │    │          │
│          │    │         │    │ │off inside │ │    │          │
│          │    │         │    │ └───────────┘ │    │          │
└──────────┘    └─────────┘    └───────────────┘    └──────────┘

6.3 Regras

  • Timeout é a policy mais externa (nunca esperar mais que o limite)
  • Circuit breaker envolve retry (se circuit abrir, nem tenta retry)
  • Retry usa exponential backoff com jitter
  • Retry apenas em erros transientes (5xx, timeout, ECONNRESET): nunca em 4xx
  • Quando circuit abrir: logar warning, incrementar métrica, retornar erro descritivo
  • Fallback opcional: dados em cache quando disponível (ex: parâmetros municipais NFSe)

6.4 Estrutura

src/
├── common/
│   └── resilience/
│       ├── resilience.module.ts
│       ├── policies/
│       │   ├── nfse.policy.ts
│       │   ├── cnpja.policy.ts
│       │   ├── oauth.policy.ts
│       │   ├── email.policy.ts
│       │   ├── sms.policy.ts
│       │   └── storage.policy.ts
│       └── resilience.service.ts        # Factory de policies

7. Cache

Redis como store centralizado para cache de leitura, locks distribuídos e session do BullMQ.

7.1 Infraestrutura

PropriedadeValor
EngineRedis 7+ (ElastiCache)
Biblioteca@nestjs/cache-manager + cache-manager-redis-yet
SerializaçãoJSON
Key prefixcontrasync:cache:
Lock prefixcontrasync:lock:

7.2 Pontos de Cache

Endpoint / DadoTTLEstratégia de Invalidação
GET /contracts/:id30sInvalidar no update/delete
GET /providers/:id/compliance60sInvalidar em mudança de status
GET /dashboard/*5minInvalidar por TTL
GET /invoices (listagem)30sInvalidar na criação/update
Parâmetros municipais NFSe24hInvalidar por TTL
Alíquotas de serviço NFSe24hInvalidar por TTL
Company settings5minInvalidar no update
Permission profiles5minInvalidar no update
Certificado digital (agente HTTPS)ExistenteManter padrão atual

7.3 Padrão de Invalidação

Cache-Aside Pattern:

  READ:
    1. Buscar no Redis
    2. Se encontrou → retornar (cache hit)
    3. Se não → buscar no banco → salvar no Redis → retornar

  WRITE:
    1. Atualizar no banco
    2. Deletar chave do Redis (invalidação)
    3. Próximo read reconstrói o cache

7.4 Regras

  • Nunca cachear dados sensíveis (tokens, certificados, senhas)
  • Sempre incluir companyId na chave para respeitar multi-tenancy
  • Formato da chave: contrasync:cache:{entity}:{companyId}:{id}
  • Listagens: contrasync:cache:{entity}:list:{companyId}:{hash-dos-filtros}
  • Usar @CacheKey() e @CacheTTL() nos controllers quando possível
  • Cache não substitui paginação, só cachear resultados já paginados

7.5 Locks Distribuídos

Para operações que não devem rodar em paralelo (ex: cron jobs em múltiplas instâncias):

OperaçãoLock KeyTTL do Lock
Compliance croncontrasync:lock:cron:compliance5min
Contract overdue croncontrasync:lock:cron:contract-overdue2min
Contract PDF croncontrasync:lock:cron:contract-pdf10min
Onboarding croncontrasync:lock:cron:onboarding5min
NFSe emission (por invoice)contrasync:lock:nfse:{invoiceId}2min

7.6 Estrutura

src/
├── config/
│   ├── redis.config.ts
│   └── cache.config.ts
├── common/
│   └── cache/
│       ├── cache.module.ts
│       └── cache-invalidation.service.ts

8. Filas, Idempotência e Dead-Letter Queue

Processamento assíncrono via BullMQ com garantia de idempotência e DLQ para falhas.

8.1 Filas e Seus Propósitos

FilaProcessadorConcorrênciaPrioridade
emailEnvio de emails (SES)5Normal
pdf-generationGeração de PDFs (Puppeteer)2Normal
nfse-emissionEmissão de NFSe3Alta
nfse-queryConsulta de status NFSe5Normal
push-notificationPush via Expo SDK10Normal
compliance-checkProcessamento de deadlines3Normal
onboarding-reminderEnvio de lembretes5Baixa

8.2 Migração dos Cron Jobs para Filas

ANTES (Cron direto):
  @Cron('0 6 * * *')
  processCompliance() → executa tudo sincronamente

DEPOIS (Cron como producer):
  @Cron('0 6 * * *')
  scheduleCompliance() → enfileira jobs individuais

  @Processor('compliance-check')
  processOne(job) → processa um compliance por vez com retry

8.3 Idempotência

MecanismoAplicação
jobId no BullMQPrevine jobs duplicados na fila
Idempotency key em endpointsPrevine criação duplicada via API
Lock distribuído (Redis)Previne execução paralela de cron schedulers
Status check antes de processarVerificar se job já foi processado

Idempotency Key em endpoints críticos:

EndpointHeaderFormato
POST /invoices/emitX-Idempotency-KeyUUID v4 (client-generated)
POST /contractsX-Idempotency-KeyUUID v4
POST /signing/:id/signX-Idempotency-KeyUUID v4

Regras:

  • Client envia X-Idempotency-Key no header
  • Backend armazena no Redis: contrasync:idempotency:{key} → response (TTL 24h)
  • Se key já existir, retorna response armazenada sem processar novamente
  • Aplicar apenas em endpoints de criação/mutação críticos

8.4 Dead-Letter Queue

┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│ Producer │───►│  Queue   │───►│ Consumer │───►│ Sucesso  │
│          │    │          │    │          │    │          │
└──────────┘    └──────────┘    └────┬─────┘    └──────────┘

                                     │ Falha após N retries

                               ┌──────────┐
                               │   DLQ    │
                               │          │
                               │ Alerta + │
                               │ Dashboard│
                               └──────────┘

Configuração por fila:

FilaMax RetriesBackoffDLQ
email5exponential (30s base)email-dlq
pdf-generation3exponential (60s base)pdf-generation-dlq
nfse-emission5exponential (60s base)nfse-emission-dlq
push-notification3exponential (10s base)push-notification-dlq
compliance-check3exponential (30s base)compliance-check-dlq
onboarding-reminder3exponential (30s base)onboarding-reminder-dlq

Regras da DLQ:

  • Jobs que falharem após max retries vão automaticamente para a DLQ
  • Alerta quando DLQ depth > 50
  • Endpoint admin para listar, inspecionar e reprocessar jobs da DLQ
  • Reter jobs na DLQ por 7 dias

8.5 Estrutura

src/
├── config/
│   └── queue.config.ts
├── common/
│   ├── queue/
│   │   ├── queue.module.ts              # Registro global das filas
│   │   └── dlq.service.ts              # Gestão de DLQ
│   └── idempotency/
│       ├── idempotency.guard.ts         # Guard para X-Idempotency-Key
│       └── idempotency.service.ts       # Armazenamento Redis
├── modules/
│   ├── invoices/
│   │   └── processors/
│   │       ├── nfse-emission.processor.ts
│   │       └── nfse-query.processor.ts
│   ├── contracts/
│   │   └── processors/
│   │       └── pdf-generation.processor.ts
│   ├── notifications/
│   │   └── processors/
│   │       └── push-notification.processor.ts
│   └── compliance/
│       └── processors/
│           └── compliance-check.processor.ts

9. Health Checks

Health check profundo que verifica todos os componentes críticos.

9.1 Endpoints

EndpointUsoVerifica
GET /healthLoad balancer (Elastic Beanstalk)Status básico
GET /health/readyReadiness probeDB + Redis + filas
GET /health/liveLiveness probeProcesso está respondendo

9.2 Checks do Readiness

CheckBibliotecaThreshold
PostgreSQL@nestjs/terminus PrismaHealthIndicatorTimeout 3s
Redis@nestjs/terminus RedisHealthIndicatorTimeout 2s
BullMQ QueuesCustom health indicatorQueue respondendo
Disk@nestjs/terminus DiskHealthIndicator< 90% uso
Memory@nestjs/terminus MemoryHealthIndicatorHeap < 512MB

9.3 Estrutura

src/
├── common/
│   └── health/
│       ├── health.module.ts
│       ├── health.controller.ts
│       └── indicators/
│           ├── redis.health.ts
│           └── queue.health.ts

10. Autoscaling

Políticas de scaling baseadas em métricas reais da aplicação.

10.1 Métricas de Scaling

MétricaThreshold Scale UpThreshold Scale DownCooldown
CPU> 70% por 3min< 30% por 10min5min
Memória> 80% por 3min< 40% por 10min5min
Request Latency p99> 3s por 5min< 1s por 15min10min
Queue Depth total> 100 por 3min< 10 por 10min5min
Active Connections> 80% max por 3min< 30% max por 10min5min

10.2 Limites

PropriedadeValor
Instâncias mínimas2
Instâncias máximas8
Tipo de instânciaConfigurável via Elastic Beanstalk
Health check grace period300s

10.3 Separação de Workers

┌─────────────────────────────────────────────────────┐
│                  DEPLOY TOPOLOGY                     │
├─────────────────────────────────────────────────────┤
│                                                      │
│  ┌────────────────────────────────────────────┐      │
│  │         API Servers (Elastic Beanstalk)    │      │
│  │                                            │      │
│  │  - Controllers, Services, Repositories     │      │
│  │  - WebSocket Gateway                       │      │
│  │  - Queue Producers                         │      │
│  │  - Escala por CPU/Memória/Latency          │      │
│  │  - Min: 2, Max: 8                          │      │
│  └────────────────────────────────────────────┘      │
│                         │                            │
│                    Redis (ElastiCache)                │
│                         │                            │
│  ┌────────────────────────────────────────────┐      │
│  │         Queue Workers (EC2 / ECS)          │      │
│  │                                            │      │
│  │  - BullMQ Processors                       │      │
│  │  - Cron Schedulers (apenas 1 instância)    │      │
│  │  - Escala por Queue Depth                  │      │
│  │  - Min: 1, Max: 4                          │      │
│  └────────────────────────────────────────────┘      │
│                                                      │
└─────────────────────────────────────────────────────┘

11. Variáveis de Ambiente

VariávelObrigatóriaExemploDescrição
REDIS_HOSTSimredis.internalHost do Redis
REDIS_PORTNão6379Porta do Redis (default 6379)
REDIS_PASSWORDProd****Senha do Redis
REDIS_TLSNãotrueHabilitar TLS (prod)
LOG_LEVELNãowarnNível de log (default: info)
OTEL_EXPORTER_OTLP_ENDPOINTNãohttp://collector:4318Endpoint do OpenTelemetry
OTEL_TRACES_SAMPLE_RATENão0.1Taxa de amostragem de traces
QUEUE_CONCURRENCY_DEFAULTNão3Concorrência padrão dos workers
CACHE_DEFAULT_TTLNão30TTL padrão do cache em segundos
PROCESS_MODENãoapiModo: api, worker, ou all
THROTTLE_DISABLEDNãotrueDesliga o rate limit (local/dev)
THROTTLE_LIMITNão300Requisições por janela, por usuário/IP
THROTTLE_TTL_MSNão60000Janela do rate limit em ms
TRUST_PROXY_HOPSNão1Proxies confiáveis na frente da API

12. Ordem de Implementação

Fase 1: Fundação (Quick Wins)

1. Correlation ID + AsyncLocalStorage
2. Pino (substituir Logger nativo)
3. Redis connection + config
4. Health checks profundos (@nestjs/terminus)

Fase 2: Resiliência

5. Timeout em todas chamadas externas
6. Retry com backoff (cockatiel)
7. Circuit breaker por serviço externo
8. Cache nos endpoints de leitura críticos

Fase 3: Processamento Assíncrono

9.  BullMQ setup + filas
10. Migrar cron jobs para queue producers
11. Processors com DLQ
12. Idempotency guard
13. Locks distribuídos nos cron schedulers

Fase 4: Observabilidade Completa

14. OpenTelemetry SDK + métricas custom
15. Métricas de negócio
16. Alarmes CloudWatch
17. Autoscaling policies avançadas

13. Checklist de Qualidade

Para cada componente implementado:

  • [ ] Correlation ID propagado em logs e chamadas externas
  • [ ] Logs estruturados em JSON com campos obrigatórios
  • [ ] Dados sensíveis redatados nos logs
  • [ ] Chamadas externas com timeout explícito
  • [ ] Chamadas externas com circuit breaker
  • [ ] Retry apenas em erros transientes (5xx, timeout)
  • [ ] Cache com invalidação correta e multi-tenancy na chave
  • [ ] Jobs de fila idempotentes
  • [ ] DLQ configurada para toda fila
  • [ ] Health check cobrindo todos os componentes
  • [ ] Métricas emitidas para operações críticas

Interceptação de Email e SMS em Desenvolvimento

Em ambiente de desenvolvimento, todo envio de email e SMS pode ser redirecionado para um destinatário fixo, evitando envios acidentais para usuários reais.

Variáveis de Ambiente

VariávelDescriçãoExemplo
INTERCEPT_EMAIL_VERIFICATIONRedireciona todos os emails para este endereço. Quando preenchida, o código de verificação de email usa o bypass 123456.[email protected]
INTERCEPT_SMS_VERIFICATIONRedireciona todos os SMS para este número. Quando preenchida, o código de verificação de SMS usa o bypass 123456.31999999999
BYPASS_CNPJ_CARD_VALIDATIONtrue libera o upload do cartão CNPJ para qualquer PDF: o controller aceita ?document=<cnpj>, o service pula scrape/validate e popula os dados da empresa via CompanyLookupService.lookup (CNPJa).true

Comportamento

  • Variável vazia ou ausente: envio normal para o destinatário original.
  • Variável preenchida: todo envio é redirecionado para o valor configurado. O destinatário original é registrado no log como warn.
  • A interceptação é centralizada nos services EmailService e SmsService: nenhum chamador precisa tratar isso.
  • Códigos de verificação (email e SMS) usam o bypass 123456 quando a interceptação está ativa.
  • BYPASS_CNPJ_CARD_VALIDATION: só ativa se o controller receber a query ?document=<cnpj>. Sem o param, o scrape+validate normal é executado mesmo com a flag ligada. O file uploadado continua sendo persistido (auditoria preservada). Ativação gera log warn BYPASS_CNPJ_CARD_VALIDATION ativo: scrape+validate ignorados....

Exemplo de .env

env
[email protected]
INTERCEPT_SMS_VERIFICATION=31999999999
BYPASS_CNPJ_CARD_VALIDATION=true

Endpoint Dev de Upgrade de Plano

POST /subscriptions/dev/upgrade é um atalho para QA promover uma empresa a um plano pago sem rodar o checkout real (Mercado Pago) nem mexer no banco via SQL. Mesmo padrão do bypass de OTP 123456 e do BYPASS_CNPJ_CARD_VALIDATION: só existe fora de produção.

Comportamento

  • Quando NODE_ENV === 'production', o handler retorna 404 NotFound antes de qualquer side-effect. A rota some efetivamente em produção.
  • Em dev/staging, marca como @Public() (sem JWT/owner role): chamada via curl é suficiente.
  • A subscription é criada como FREE inativa antes do upgrade caso ainda não exista (ensureCompanyHasSubscription).
  • applyPaidPlan no repository: status → ACTIVE, periodicidade → MONTHLY, renewAt → +30 dias, subscribedFor += 1, providerQtd = valor recebido (default 1).
  • Logger SubscriptionService registra Dev upgrade applied company=<id> plan=<code> providerQtd=<n> em sucesso e warn em rejeições (plano não encontrado, plano FREE).

Request / Response

http
POST /api/v1/subscriptions/dev/upgrade
Content-Type: application/json

{ "companyId": "479206ac-...", "planCode": "SYNC", "providerQtd": 2 }
  • planCode: enum SubscriptionPlanType (FREE, SYNCLOW, SYNC, HYPER_SYNC, CUSTOM). FREE é rejeitado com 400, use POST /subscriptions/cancel para reverter.
  • providerQtd: opcional, default 1, mínimo 1.

Resposta 201: SubscriptionResponseDto com plano e quotas resolvidos.

Erros

StatusCenário
400planCode inválido / companyId não-UUID / planCode=FREE
404Plano informado não encontrado / NODE_ENV=production

Documento atualizado em Maio 2026


Rate limiting (ThrottlerGuard)

ConditionalThrottlerGuard (src/common/throttle/) é global via APP_GUARD e roda depois de JwtAuthGuard e CompanyGuard, então já enxerga request.user. O BFF tem um guard equivalente.

Como o tráfego é agrupado

TráfegoChave (tracker)Por quê
Autenticadouser:<id>O core só recebe conexões do BFF; agrupar por IP colocaria a plataforma inteira num balde só
Anônimo (login, OTP, portais públicos)ip:<ip real>Protege brute force sem depender de sessão

O IP real sai de resolveClientIp (src/common/utils/ip.util.ts): usa o hop de X-Forwarded-For mais próximo do servidor (req.ips[last]), com fallback para req.ip e para o socket. Isso só funciona com app.set('trust proxy', appConfig.trustProxyHops) no main.ts; TRUST_PROXY_HOPS conta os proxies confiáveis (Caddy = 1, e o BFF repassa o mesmo X-Forwarded-For para o core). Aumentar esse número sem ter o proxy correspondente permite que o cliente forje o IP e escape do limite; diminuir joga todos os clientes na mesma chave e devolve 429 para todo mundo.

Limites

EscopoDefaultOnde
GlobalTHROTTLE_LIMIT (300) por THROTTLE_TTL_MS (60s)ThrottlerModule.forRoot em app.module.ts
Rotas públicas de auth20 por 5 minPUBLIC_AUTH_THROTTLE em src/common/throttle/public-throttle.const.ts
Rotas sensíveis@Throttle({ default: { ttl, limit } }) no controllerEx.: signing, lookup, contract-part-search

Quando o guard não aplica

CondiçãoEfeito
THROTTLE_DISABLED=truePula o rate limit inteiro (local, dev e demo)
Ambiente não é produção real (isRealProduction)Pula o rate limit
Header x-internal-agent batendo INTERNAL_API_KEY e requisição autenticadaIsenta o tráfego interno do Agente

Resposta do 429

getErrorMessage devolve texto pronto para o usuário (buildThrottleMessage), no lugar do ThrottlerException: Too Many Requests do pacote: "Muitas tentativas em pouco tempo. Aguarde N segundos e tente novamente." O guard também envia Retry-After (em segundos); o BFF repassa esse header e o expõe no CORS (exposedHeaders) para o front conseguir ler o tempo de espera e montar a contagem regressiva.