# Auditoria técnica — IB, Merchant, PIX, TED/SPB e API pública

Data da auditoria: 24/07/2026
Fuso usado na linha do tempo: America/Sao_Paulo (UTC-3)
Modo de execução: leitura de código, artefatos publicados, testes e AWS em modo read-only

## 1. Escopo e regra de verdade

Esta auditoria cobre:

- `core/apps/banking` (`ib-front`);
- `core/apps/merchant` (`merchant-front`);
- rotas e controllers correspondentes no Core;
- integração Core ↔ cabine PIX/DICT/QR Code;
- integração Core ↔ cabine SPB para TED;
- API pública documentada em `https://docs.monetarie.com` e servida por
  `https://api.monetarie.com`;
- configuração e evidências operacionais em produção na AWS.

Está explicitamente fora do contrato público analisado:

- `docs.monetarie.internal`;
- Partner API;
- rotas `/api/partner/v1`.

Esses materiais foram separados da API pública para não misturar autenticação,
rotas ou semânticas diferentes.

### Classificação das evidências

| Código | Significado |
|---|---|
| PROD | observado diretamente nos serviços/logs/artefatos de produção |
| CODE | demonstrado no código-fonte citado |
| LIVE | observado na documentação ou artefato público publicado |
| TEST | demonstrado por execução local de teste/comando |
| LIMIT | não há evidência suficiente para atribuição ao incidente relatado |

Nenhum CPF, CNPJ, número de conta, e-mail, identificador interno de usuário ou
segredo foi reproduzido neste relatório.

## 2. Conclusão executiva

O fluxo não está 100% correto. Há um defeito crítico e ativo na cabine PIX que
explica a tentativa de criação de chave observada em produção:

1. o Core publicou uma única solicitação NATS;
2. as três réplicas da cabine PIX consumiram a mesma solicitação;
3. cada réplica chamou o endpoint de criação de entrada do DICT/Bacen;
4. o Core recebeu primeiro uma resposta de conflito e devolveu HTTP `422`;
5. outra réplica concluiu a criação e publicou o evento de chave criada logo
   depois;
6. o frontend, por ter recebido `422`, não recarregou a lista.

Isto não é uma inferência: a multiplicação `1 → 3`, as respostas excedentes e o
evento de criação aparecem nos logs de produção; a ausência de `queue_group` nos
assinantes NATS aparece no código da mesma versão PIX implantada.

Além desse incidente, foram confirmados gaps críticos ou altos em:

- normalização do status da chave (`ACTIVE` versus `active`);
- escolha da conta de débito em TED com múltiplas contas;
- rotas de transferência do `merchant-front`;
- idempotência de TED/TEF;
- exibição de dados DICT não recebidos como se fossem reais;
- respostas de indisponibilidade apresentadas como listas vazias ou dados
  fabricados;
- geração de QR Code declarada como concluída sem exigir imagem ou BR Code;
- fallback para documento do titular sem comprovar que ele é uma chave DICT;
- semântica de erros do MED na API pública;
- promessa `X-Key-Case: camelCase` não cumprida para respostas JSON em iodata;
- incompatibilidade de Availability Zones da cabine PIX com o seu target group;
- dados pessoais em texto claro em logs de produção gerados por uma execução
  diagnóstica.

### 2.1 Atualização de remediação local

Após a emissão do diagnóstico, as correções P0/P1 abaixo foram implementadas
localmente na branch isolada `audit/ib-merchant-pix-ted-2026-07-24`. Elas ainda
não representam estado de produção:

| Achado | Estado local |
|---|---|
| F-01 fan-out NATS | queue groups estáveis + `operation_id` + deduplicação persistente e migration |
| F-02 conta TED | `sourceAccountId` obrigatório, com posse e estado ativo validados |
| F-03/F-04 chaves | status canônico, sem chave sintética e erros fail-closed |
| F-05 TED/TEF Merchant | rota própria corrigida; branch sem contrato “outros” removida; dados TED obrigatórios |
| F-06 QR | exige chave ativa da conta e BR Code não vazio antes de sucesso |
| F-07 identidade DICT | Merchant rejeita payload incompleto sem inventar nome/instituição |
| F-08 MED público | `502` para indisponibilidade, `404` apenas para ausência/escopo |
| F-09 KeyCase | suporte a iodata e conversão de respostas JSON 2xx/4xx/5xx |
| F-10 idempotência | chave estável por intenção em TED/TEF nos dois portais |
| F-18 expiração | documentação pública alinhada ao default real de 15 minutos |

Continuam pendentes por dependerem de mudança externa ou decisão explícita:

- F-11, topologia de Availability Zones;
- F-12, saneamento operacional dos logs com PII;
- F-13 a F-16, gaps de produto/contratos de menor prioridade;
- F-17, decisão sobre iniciação pública de TED.

O procedimento de implantação e aceite está em
`docs/operator/2026-07-24-rollout-ib-merchant-pix-ted-api-publica.md`.

## 3. Incidente comprovado da chave PIX

### 3.1 Linha do tempo de produção

| Horário BRT | Evidência |
|---|---|
| 15:52:34 | `GET .../pix/keys` respondeu `200`; duas respostas NATS adicionais foram descartadas como “request no longer registered” |
| 15:52:55 | ocorreu uma única requisição `POST .../pix/keys` no Core |
| 15:52:55 | a cabine PIX efetuou três `POST /api/v2/entries/` ao DICT/Bacen no mesmo segundo |
| 15:52:56 | o Core enviou HTTP `422` |
| 15:52:56 | duas respostas NATS adicionais chegaram após a primeira resposta já ter encerrado a requisição |
| 15:52:56 | uma réplica publicou `monetarie.dict.keys.created` |

Consultas agregadas e sem PII ao CloudWatch confirmaram:

- três chamadas de criação ao DICT/Bacen no intervalo;
- duas respostas NATS excedentes no `GET`;
- duas respostas NATS excedentes no `POST`;
- um evento de chave criada;
- uma requisição de criação no Core seguida de uma resposta `422`.

### 3.2 Causa no código

O assinante de comandos usa assinatura NATS simples:

- `pix/backend/apps/dict_service/lib/dict_service/nats/dict_api_responder.ex:29-36`
  chama `Gnat.sub(conn, self(), @api_subject)` sem grupo de fila;
- cada mensagem inicia uma task e cada task publica resposta para o mesmo
  `reply_to`: linhas 50-75;
- o assunto é `dict.api.request` e inclui operações mutáveis de chave, claims,
  recoveries e infrações;
- `pix/backend/apps/dict_service/lib/dict_service/nats/dict_lookup_responder.ex:29-39`
  repete o mesmo padrão, sem grupo de fila, para `dict.lookup.request` e
  `dict.list_keys.request`.

Com três tasks ECS, NATS entrega uma cópia a cada assinante comum. Um request/reply
aceita a primeira resposta; as duas seguintes tornam-se respostas não registradas.
Para mutações, as três réplicas executam o efeito externo antes de responder.

### 3.3 Efeito na tela

`createPixKey` lança erro ao receber o `422`. A view somente refaz o `GET` quando
a criação retorna sucesso. Portanto, a tela não mostra a chave que foi criada por
outra réplica.

Há um segundo contrato incompatível:

- a cabine grava `status: "ACTIVE"` em
  `pix/backend/apps/dict_service/lib/dict_service/keys.ex:83-87`;
- os serializers preservam `ACTIVE`;
- o Core preserva o valor em
  `core/backend/lib/monetarie_web/controllers/v2/pix_controller.ex:1090-1103`;
- os dois fronts preservam o valor recebido;
- o IB filtra apenas `k.status === 'active'` em
  `core/apps/banking/src/views/pix/PixReceiveView.vue:29-35`.

Consequência comprovada pelo contrato: mesmo depois de listada, uma chave
`ACTIVE` não entra no seletor de chave da tela “Receber PIX”.

O response da criação também usa o campo superior `result["status"]`, cujo valor
é `"ok"`, em vez de `result["key"]["status"]`, cujo valor é `"ACTIVE"`:
`core/backend/lib/monetarie_web/controllers/v2/pix_controller.ex:1182-1193`.

## 4. Mapa dos contratos

### 4.1 Separação correta entre fronts e API pública

Os dois fronts usam:

- base URL `/api`;
- autenticação por cookie;
- `withCredentials`;
- token CSRF nas mutações;
- header `X-Merchant-Id` conforme o portal.

Referências:

- `core/apps/banking/src/lib/api.ts:1-43`;
- `core/apps/merchant/src/http/api.ts:1-44`.

Eles são clientes do Core interno publicado atrás do mesmo domínio, e não
clientes diretos da API pública. Isto está correto: a API pública exige API Key e,
para POSTs financeiros, HMAC e idempotência; um segredo HMAC não deve ser
embutido no navegador.

### 4.2 PIX no IB e Merchant

| Operação | Front → Core | Core → domínio/cabine | Situação |
|---|---|---|---|
| Consulta DICT | `GET /api/pix/dict` | NATS `dict.lookup.request` | rota existe; fan-out NATS e gaps de representação |
| Envio PIX | `POST /api/accounts/:id/pix/send` ou rota merchant equivalente | pipeline PIX/cabine | rota conectada; revisar idempotência por cliente |
| Gerar QR | `POST /api/pix/qrcode` | gateway da cabine QR | conectado; sucesso visual não é validado |
| Ler copia-e-cola | `POST /api/pix/qrcode/parse` | parser local | QR dinâmico apenas com URL é rejeitado |
| Listar chaves | `GET /api/accounts/:id/pix/keys` | NATS `dict.list_keys.request` | conectado; fan-out, status e falso fallback |
| Criar chave | `POST /api/accounts/:id/pix/keys` | NATS `dict.api.request` → DICT/Bacen | P0: uma chamada vira três |
| Excluir chave | `DELETE /api/accounts/:id/pix/keys/:id` | NATS `dict.api.request` → DICT/Bacen | exposto ao mesmo P0 |
| Claims | `/api/accounts/:id/dict/claims` | DICT/cabine | exposto ao mesmo fan-out de comandos |
| Taxa PIX | `POST /api/pix/fee-preview` | Core | conectado; não devolve limites esperados pelas views |
| Limites | `GET /api/limits` | Core | conectado; usa a primeira conta, não necessariamente a selecionada |
| Agendados IB | `/api/accounts/:id/pix/scheduled-payments` | agendamento PIX pontual | contrato correspondente |
| “Agendados” Merchant | `/api/pix/scheduled` | `PixAutomatico.Instruction` | semântica diferente da tela genérica |

### 4.3 TED e transferência entre contas

| Operação | Contrato esperado | Implementação observada | Situação |
|---|---|---|---|
| TED IB/merchant | debitar a conta selecionada | controllers escolhem a primeira conta elegível do usuário | crítico em usuário com múltiplas contas |
| TEF “minhas contas” IB | `POST /api/transfers/between-accounts` | IB usa essa rota | correto no IB |
| TEF “minhas contas” Merchant | mesma rota dedicada | confirmação posta em `/merchants/:id/transfers` | rota errada |
| TEF “outros” Merchant | resolver destinatário ou coletar dados válidos | chama `GET /api/transfers/lookup-account` | rota não existe |
| TED Merchant | nome, documento, ISPB, agência e conta | formulário permite avançar sem nome/documento | backend pode rejeitar com `422` |
| Idempotência | chave por intenção de pagamento | TED/TEF não enviam `Idempotency-Key` | duplicação por duplo clique/retry possível |

O endpoint correto entre contas existe em
`core/backend/lib/monetarie_web/router.ex:3546-3548`.

O `merchant-front` chama uma rota inexistente em
`core/apps/merchant/src/composables/useTransfer.ts:118-132` e a branch “outros”
depende dessa chamada em
`core/apps/merchant/src/views/transfer/TransferSendView.vue:385-408`.

A confirmação de transferência própria do Merchant chama
`selectedMerchantPath('/transfers')` em
`core/apps/merchant/src/views/transfer/TransferSendConfirmView.vue:192-204`, não
`/transfers/between-accounts`.

Nos dois caminhos TED do Core, a conta de débito é obtida por
`find_user_account`, que seleciona a primeira por ID:

- merchant:
  `core/backend/lib/monetarie_web/controllers/v2/transfer_controller.ex:75-104`;
- self:
  `core/backend/lib/monetarie_web/controllers/v2/transfer_controller.ex:724-750`;
- helper:
  `core/backend/lib/monetarie_web/controllers/v2/transfer_controller.ex:914-921`.

O Merchant envia `sourceAccountId`, mas o controller TED não o usa. No IB, a
confirmação preserva a conta selecionada em estado local, mas o payload de TED
externa não a inclui:
`core/apps/banking/src/views/transfer/TransferSendConfirmView.vue:118-128`.

O plug de idempotência deixa a chamada passar quando o header não existe:
`core/backend/lib/monetarie_web/plugs/idempotency.ex:41-44`.

## 5. Achados priorizados

### F-01 — P0 — Execução multiplicada de comandos DICT por réplica

Evidência: PROD + CODE.

Impacto observado: uma criação produziu três chamadas ao Bacen, resposta `422` ao
usuário e criação efetiva em outra réplica. O mesmo mecanismo alcança exclusão e
atualização de chaves, claims, MED e infrações.

### F-02 — Crítico — Conta selecionada não governa o débito TED

Evidência: CODE.

O Core seleciona a primeira conta do usuário. Isso pode aplicar saldo, limite,
hold e débito em conta diferente da escolhida na tela quando houver mais de uma.

### F-03 — Crítico — Status da chave quebra seleção e fallback da API pública

Evidência: CODE.

Além de ocultar `ACTIVE` no IB, a API pública:

- devolve o status sem normalização em
  `core/backend/lib/monetarie_web/controllers/external/keys_controller.ex:84-90`;
- procura apenas `status == "active"` ao escolher chave padrão em
  `core/backend/lib/monetarie_web/controllers/external/pix_controller.ex:609-627`.

Se nenhuma chave casar exatamente, o Core usa o documento do titular. A cabine
de QR Code valida apenas o formato do valor em
`pix/backend/apps/settlement_service/lib/settlement_service/qr_codes.ex:673-700`;
ela não comprova que o documento está registrado como chave DICT.

### F-04 — Crítico — Indisponibilidade de chaves vira chave fabricada ou lista vazia

Evidência: CODE.

Quando o provider falha, o Core devolve HTTP `200` com uma chave CPF/CNPJ
sintética, status ativo, ID e data gerados:
`core/backend/lib/monetarie_web/controllers/v2/pix_controller.ex:1108-1109` e
`:1325-1346`.

Nos dois fronts, qualquer erro de listagem é convertido em `{ keys: [] }`:

- `core/apps/banking/src/composables/usePix.ts:346-370`;
- `core/apps/merchant/src/composables/usePix.ts:245-269`.

Os três estados “sem chave”, “serviço indisponível” e “chave presumida” ficam
indistinguíveis.

### F-05 — Alto — Fluxos de transferência do Merchant usam contratos inválidos

Evidência: CODE.

O lookup de “outros” não possui rota no Core; a transferência própria usa o
controller TED; e o formulário TED não exige nome/documento embora o payload SPB
os exija.

### F-06 — Alto — QR Code pode ser declarado gerado sem dado utilizável

Evidência: CODE + LIMIT.

No IB, após qualquer resposta 2xx, a view lê `qr_image` e `brcode` e marca
`qrCodeGenerated = true` sem exigir que um deles exista:
`core/apps/banking/src/views/pix/PixReceiveView.vue:42-55`.

O backend garante BR Code apenas na branch de sucesso do gateway, mas a geração
da imagem captura qualquer exceção e devolve `nil` sem log:
`core/backend/lib/monetarie/use_cases/pix/qr_generation.ex:66-77` e `:134-138`.

O Merchant possui fallback visual a partir do BR Code, mas também pode anunciar
sucesso quando imagem e código estiverem vazios. O espelho local é best-effort:
uma falha de persistência é logada e a cobrança continua sendo retornada como
sucesso (`qr_generation.ex:91-122`), prejudicando correlação posterior com
pagamento.

LIMITAÇÃO: não foi encontrada hoje, na janela auditada, uma chamada de produção
`POST /api/pix/qrcode` associável ao relato. Portanto, este relatório não atribui
uma causa histórica específica ao QR Code relatado; registra os defeitos
reproduzíveis acima.

### F-07 — Alto — Merchant inventa identidade/instituição na resposta DICT incompleta

Evidência: CODE.

`core/apps/merchant/src/composables/usePix.ts:169-183` usa:

- a própria chave como nome do favorecido;
- `"Monetarie"` como banco;
- `"999"` como código.

O IB, corretamente, rejeita respostas sem nome, documento, instituição, tipo e
chave (`core/apps/banking/src/composables/usePix.ts:156-225`).

O backend pode resolver dados autoritativos novamente no envio, portanto não foi
demonstrado desvio de fundos. O defeito comprovado é exibir/confirmar identidade
não recebida do DICT e permitir progressão com dado falso.

### F-08 — Alto — API pública mascara falhas do MED

Evidência: CODE + LIVE.

`External.MedController` transforma erro/timeout da listagem em HTTP `200`,
`worked: true`, `meds: []`, e transforma erro/timeout do detalhe em `404`:
`core/backend/lib/monetarie_web/controllers/external/med_controller.ex:62-124`.

Um cliente não consegue distinguir “nenhum MED”, “cabine indisponível” e “MED não
encontrado”.

### F-09 — Alto — `X-Key-Case` não cumpre o contrato para JSON real

Evidência: CODE + TEST.

Os fronts pedem `X-Key-Case: camelCase`, mas o plug só converte quando
`resp_body` é binário:
`core/backend/lib/monetarie_web/plugs/key_case.ex:29-51`.

Phoenix 1.8 serializa JSON como iodata. Um teste direto do plug com
`Phoenix.Controller.json/2` retornou `outer_key/inner_key`, sem camelização.
Controllers que já montam campos camelCase mascaram parcialmente o problema.
Erros não são convertidos por desenho, pois o plug limita a conversão a `2xx`.

Correção de hipótese durante a auditoria: isso prova que `qr_image` permanece
snake_case e que a leitura `data.qr_image` do IB está alinhada. A hipótese
preliminar de que o header transformaria esse campo foi descartada pelo teste e
não é usada como achado.

### F-10 — Alto — TED/TEF sem chave de idempotência

Evidência: CODE.

Os fronts não enviam `Idempotency-Key` nesses fluxos. O pipeline possuir o plug
não cria idempotência quando o header está ausente.

### F-11 — Alto — Cabine PIX usa AZ não habilitada no load balancer

Evidência: PROD.

O serviço ECS PIX está configurado para subnets em `sa-east-1a`, `1b` e `1c`; o
ALB/target group público do QR Code está habilitado apenas em `1a` e `1b`.
Eventos do ECS em 24/07 registraram targets em Availability Zone não habilitada e
substituição de tasks que caíram em `1c`.

No encerramento da auditoria:

- rollout PIX estava `COMPLETED`;
- desired/running: `3/3`;
- três targets estavam saudáveis;
- as tasks correntes estavam apenas em `1a` e `1b`.

O estado atual está saudável por alocação do scheduler, mas a configuração
continua permitindo recorrência.

### F-12 — Alto/Segurança — PII em logs de produção

Evidência: PROD.

O log group do Core continha saída diagnóstica com linhas completas de usuários,
incluindo dados pessoais em texto claro. A evidência aponta para uma execução
diagnóstica/ad hoc; não foi demonstrado que o logger normal de requisições produz
essas linhas.

Ação necessária: remover o acesso indevido por política de retenção aplicável,
identificar o executor/task, proibir dumps de registros e adicionar redaction.

### F-13 — Médio — Copia-e-cola não suporta QR dinâmico URL-only

Evidência: CODE.

O parser responde `422 url_only_qr_unsupported` para QR dinâmico cuja informação
autoritativa precisa ser consultada pela URL:
`core/backend/lib/monetarie_web/controllers/v2/pix_controller.ex:908-920`.

### F-14 — Médio — Preview de taxa e limites têm contratos desconectados

Evidência: CODE.

O preview retorna valor, taxa, total e moeda. As views procuram opcionalmente
`response.data.limits`, que o endpoint não fornece, de modo que a prevenção de
limite na etapa de preview fica inativa. A cobrança final ainda passa pelo gate
de limites do backend.

Há também cálculo latente no Merchant que multiplica centavos por 100 ao comparar
com um eventual campo `limits`; hoje ele não é acionado porque esse campo não
existe.

### F-15 — Médio — “PIX agendado” tem semântica diferente entre portais

Evidência: CODE.

O IB lista pagamentos PIX pontuais agendados. O Merchant usa `/pix/scheduled`,
cujo controller lista instruções de Pix Automático. A tela genérica não explicita
essa diferença de produto.

### F-16 — Médio — Escopo de chave é documental, não da conta selecionada

Evidência: CODE.

A listagem enviada ao provider filtra por documento do titular e merchant, mas
não pela conta selecionada. Usuários com várias contas do mesmo titular podem
ver a mesma lista de chaves em todas elas. A exclusão também não demonstra posse
da chave pela conta selecionada, apenas pelo escopo documental.

### F-17 — Lacuna de produto/contrato — API pública não inicia TED

Evidência: CODE + LIVE.

As rotas públicas em `/api/external` oferecem saldo, extrato/transações, consulta
de chaves, MED, webhooks e os POSTs de PIX cash-out, cash-in e refund, além dos
demais serviços documentados. Não existe endpoint público de iniciação TED.

TED aparece em consulta/extrato e eventos, mas não há contrato público para
criá-la. Se clientes finais devem iniciar TED por `api.monetarie.com`, o produto
e a documentação estão incompletos; não se deve reutilizar a Partner API para
preencher essa lacuna silenciosamente.

### F-18 — Médio — Expiração padrão do PIX cash-in diverge da documentação

Evidência: CODE + LIVE.

A documentação pública declara a precedência:

1. valor enviado no request;
2. configuração da conta;
3. 15 minutos.

O código usa diretamente 900 segundos quando o request omite o campo, sem
consultar configuração da conta:
`core/backend/lib/monetarie_web/controllers/external/pix_controller.ex:576-587`.

## 6. API pública: paridade e divergências

Fontes corretas:

- [Documentação pública](https://docs.monetarie.com/)
- [Chaves PIX](https://docs.monetarie.com/pix-keys)
- [PIX cash-in](https://docs.monetarie.com/pix-cashin)
- [Autenticação](https://docs.monetarie.com/auth-token)
- host de API: `https://api.monetarie.com`

O host raiz da API responder `404` JSON não indica indisponibilidade; os recursos
válidos estão abaixo de `/api/external`.

O script `scripts/check_external_docs_parity.sh` passou:

- 20 rotas `/api/external` presentes nas coleções verificadas;
- eventos presentes nas três linguagens.

O bundle publicado em `docs.monetarie.com` e a coleção Postman publicada
corresponderam aos artefatos locais. Esse teste garante presença de
rota/evento, não equivalência semântica dos payloads. Por isso ele não detecta
F-03, F-08 ou F-18.

No cash-in público, o código pode responder `worked: true` e
`qr_code_image: ""` se a codificação da imagem falhar; o `qr_code` textual
continua sendo o dado obrigatório na branch de sucesso. O cliente da API deve
renderizar a partir do BR Code quando a imagem não vier, mas o servidor também
deve observar e reportar a falha de codificação.

## 7. AWS e versões observadas

Ambiente: cluster ECS de produção em `sa-east-1`, acesso read-only.

| Serviço | Revisão observada | Imagem/tag observada |
|---|---:|---|
| Core API | 104 | `prod-d2b0cdf3-mfamembers-20260724` |
| IB front | 16 | `prod-mfa-otpsetup-20260724` |
| Merchant front | 11 | `prod-b2ef4763-onboarding-20260724` |
| Cabine PIX | 81 | `prod-47e09fcd-purpcd-20260724` |
| Cabine SPB | 44 | `prod-defeito7lpi-20260724` |

Configuração sensível foi inspecionada sem registrar valores. Foi confirmado em
produção:

- simulador PIX desabilitado;
- integração Bacen/DICT real habilitada;
- NATS habilitado;
- integração de mensageria SPB habilitada.

O DLQ do Core tinha 15 itens para limiar operacional 10. O conteúdo não foi
classificado nesta auditoria; portanto, ele não é atribuído a PIX ou TED.

## 8. Validações executadas

| Validação | Resultado |
|---|---|
| Typecheck `banking` | passou |
| Typecheck `merchant` | passou |
| Testes `banking` | 36 arquivos, 264 testes, todos passaram |
| Testes `merchant` | 11 arquivos, 31 testes, todos passaram |
| Testes backend direcionados | 72 testes, todos passaram |
| Paridade rotas/eventos da API pública | passou, 20 rotas |
| Teste direto `KeyCase` com JSON iodata | falhou o contrato: manteve snake_case |
| Consulta CloudWatch agregada do incidente | confirmou 1 request Core, 3 criações DICT e respostas excedentes |

Validação posterior às correções locais:

| Validação | Resultado |
|---|---|
| Typecheck + suíte completa `banking` | 36 arquivos, 264 testes, passou |
| Typecheck + suíte completa `merchant` | 11 arquivos, 33 testes, passou |
| Cabine DICT/NATS direcionada | 14 testes, passou |
| Core: PIX/QR, TED, API pública e KeyCase | 60 testes, passou |
| Paridade da documentação pública | 20 rotas e eventos em 3 linguagens, passou |

O teste de `KeyCase` que demonstrava a falha original agora possui regressão
automatizada para iodata e erros `4xx/5xx`.

Os testes existentes passarem não invalida os achados. As seguintes coberturas
estão ausentes:

- teste com três réplicas NATS e um único efeito externo;
- teste que exige queue group nos responders;
- teste de QR Code na view “Receber PIX” do IB;
- fixture com status real `ACTIVE`;
- teste de contrato entre as rotas da tela de transferência Merchant e o router;
- teste TED com duas contas e seleção explícita da origem;
- teste do plug `KeyCase` com iodata real;
- teste de indisponibilidade que proíba resposta vazia/fabricada;
- teste de idempotência de TED/TEF.

## 9. Plano de correção recomendado

### P0 — imediato

1. Adicionar queue groups estáveis aos assinantes:
   - um grupo para `dict.api.request`;
   - um grupo para `dict.lookup.request`;
   - um grupo para `dict.list_keys.request`.
2. Garantir que todas as versões durante rolling deployment usem o mesmo nome de
   grupo por assunto.
3. Adicionar idempotência própria na fronteira DICT/Bacen, com ID de operação,
   para que erro de configuração/retentativa nunca repita mutação.
4. Reconciliar a chave do usuário diretamente no DICT e no espelho local:
   confirmar quantas entradas existem, seu estado e seu vínculo; não excluir
   automaticamente.
5. Normalizar status na fronteira da cabine para um enum canônico e tornar os
   consumers tolerantes a caixa durante a migração.
6. Corrigir as AZs: habilitar `sa-east-1c` no ALB/target group ou remover a subnet
   `1c` do serviço.
7. Suspender ou restringir temporariamente mutações DICT até o queue group estar
   implantado e validado, caso a operação aceite essa janela.

### P1 — antes de liberar fluxo financeiro completo

1. Fazer o backend receber, validar posse e usar `sourceAccountId` em todo TED.
2. Corrigir o Merchant para `/transfers/between-accounts`.
3. Implementar com contrato explícito a consulta de conta “outros” ou retirar o
   branch até existir.
4. Exigir nome/documento do destinatário TED antes da confirmação.
5. Gerar e propagar `Idempotency-Key` por intenção em TED/TEF.
6. Remover fallbacks de chave sintética e listas vazias em indisponibilidade;
   retornar erro distinguível e manter o último estado apenas se marcado como
   cache stale.
7. Proibir defaults inventados no DICT do Merchant.
8. Validar existência/atividade da chave DICT antes de usá-la em QR Code.
9. Só marcar QR como gerado se houver BR Code válido; renderizar imagem localmente
   como fallback e observar falhas do encoder.
10. Corrigir `KeyCase` para iodata ou retirar a promessa/header e padronizar DTOs.
11. Fazer MED público distinguir indisponibilidade, ausência e não encontrado.
12. Alinhar a expiração da documentação com o código ou implementar a configuração
    da conta.

### P2 — endurecimento e prevenção

1. Testes E2E de IB e Merchant contra Core e cabines reais controladas.
2. Testes de contrato gerados a partir de OpenAPI para `api.monetarie.com`.
3. Métricas:
   - `requests NATS / efeitos DICT`;
   - respostas NATS tardias/duplicadas;
   - `422` por operação;
   - falhas de encoder QR;
   - falhas de espelho QR;
   - discrepância de chave Core × DICT.
4. Alertar quando uma intenção gerar mais de uma chamada externa.
5. Política e redaction de PII em logs, com revisão de retenção e acesso.
6. Classificar e tratar o DLQ sem atribuí-lo a um domínio antes de inspecionar o
   conteúdo.

## 10. Limites e estado das alterações

AWS e DICT permaneceram read-only. Nenhuma correção de produção, mutação no
DICT, exclusão de chave, alteração ECS ou deploy foi executado.

O repositório já possuía alterações locais não relacionadas antes da criação
deste relatório; elas foram preservadas. As correções de aplicação, testes,
migration, documentação pública e runbook estão somente na branch temporária
citada na seção 2.1.

O diagnóstico da chave é conclusivo para a execução correlacionada de 24/07/2026.
O relato de QR Code não foi correlacionado a uma requisição nos logs disponíveis;
qualquer atribuição histórica além dos defeitos de código documentados seria
especulação e foi deliberadamente excluída.
