# Auditoria completa do Core Partner API (2026-07-16)

Mandato do dono: depois do erro de consulta DICT do parceiro Herbeth (Vulci) e da reclamação de dados incompletos de TED, investigar TODO o Core Partner API e garantir que os fluxos de PIX e TED retornem os dados de pagador e recebedor nos campos corretos, com prova empírica em HML usando a conta do Herbeth, a chave da KANASTRA e TED real do acervo.

Design e plano: `docs/plans/2026-07-16-partner-api-auditoria-fixes-design.md`.

## 1. Resumo executivo

O erro "sem pé nem cabeça" do Herbeth tinha DUAS causas nossas compostas, e a auditoria revelou que o problema é sistêmico na Partner API: nenhum serializer de leitura (extrato, comprovante, status de PIX, status de TED, webhooks) expunha a contraparte, a TED enviada nem sequer persistia o recebedor, e o PIX-out do parceiro estava deterministicamente quebrado por falta do documento do pagador no fio. Tudo corrigido com TDD nesta sessão (código pronto, SEM deploy e SEM push, aguardando OK). O acesso do Herbeth em HML foi RESTAURADO (vínculo `users.partner_id` perdido pelo re-ETL de 08/07).

## 2. Prova empírica (HML vivo, 16/07)

- Chave EVP `4bf4c483-eaf7-4a8a-be24-f3834c2026eb` EXISTE no DICT do BACEN: titular "Qa Lifecycle", CPF 52998224725, KANASTRA CFI (ISPB 49288113), conta 34967, agência 1, CACC. Prova A/B via `lookup_for_test/2` no nó vivo do pix-api: sem documento do pagador devolve `not_found`; com documento resolve completo. Uma variável só.
- As 2 consultas do Herbeth (15:52 e 15:54 BRT) devolveram HTTP 200 `{"data":{"status":"not_found","data":null}}` em 40-50ms, com `[DictLookupResponder] BACEN lookup :bacen_external_failed` no mesmo milissegundo na cabine. Conectividade mTLS/DICT viva no mesmo minuto (DictInboundPollWorker operando).
- Conta do Herbeth (user 25100, CPF 86482958583): `partner_id` estava NULL (re-ETL de 08/07 varreu o vínculo e os clientes de teste criados por ele via API). `GET /accounts` devolvia lista vazia. RESTAURADO: `users.partner_id = 251a2375-4492-4936-924f-f10ba411fba4` (secret `monetarie/homolog/partner/herbeth-santana/api_credentials`), conta 10024216 listada, saldo fundeado com R$ 1,00 para teste.
- PIX vivo de R$ 0,01 da conta do Herbeth para a chave da KANASTRA (`PIXOUT20260716c8ca32921e03b85a9fd5`, E2E `E4602656220260716201300000000520`): REJEITADO em 2,6s com `errorReason: null` para o parceiro. Causa na cabine: `Failed to build outbound XML: documento do pagador ausente` na 1ª entrega (o payload do partner não leva `sender_document`), NAK indevido, e o redelivery morreu em `:e2e_already_used` (motivo enganoso). Dinheiro seguro: a pacs.008 nunca foi construída, bloqueios e hold liberados.
- TEDs reais enviadas pela Partner API no acervo (`TED20260713c320043c3b0168c7c09a` e `TED20260716c13ffc676c1b7caf1ba6`, conta 10024270): `metadata` só com `partner_id`, `counterparty_name` NULL. O recebedor ia apenas no fio SPB e ficava irrecuperável. TED recebida antiga (`SPBCRSTR20260710990000222`): chaves `payer_*` no metadata; TED recebida nova (spb_handler): chaves `sender_*`. O comprovante canônico só lia `payer_*`.
- Comprovante do parceiro capturado vivo: só o bloco `account` do próprio titular; sem payer, receiver ou pix.

## 3. Defeitos e correções (todos com TDD, RED antes)

| # | Defeito | Correção | Arquivos |
|---|---------|----------|----------|
| 1 | `dict_lookup` do partner nunca envia documento do pagador (DICT v2.11.0 exige PI-PayerId) | Resolve documento: `payerDocument` > titular de `accountId` (escopado ao parceiro) > documento do partner; chama `lookup_key/3`; spec OpenAPI atualizada | `partner_v1/pix_controller.ex` |
| 2 | Cabine mascara falha do lookup como `not_found` e o Core devolve 200 "de sucesso" | `:missing_pi_payer_id` vira `:missing_payer_document`; responder devolve `status=error` com motivo legível (`PAYER_DOCUMENT_REQUIRED`, `DICT_REJECTED`, `DICT_UNAVAILABLE`); `not_found` só para chave inexistente; Core mapeia not_found para 404 estruturado `key_not_found` | `dict_service/keys.ex`, `dict_lookup_responder.ex`, `partner_v1/pix_controller.ex` |
| 3 | `send_pix` do partner sem documento e nome do pagador (pacs.008 impossível de construir) | Payload à cabine leva `sender_document` e `sender_name` do titular da conta origem, como o V2 | `partner_v1/pix_controller.ex` |
| 4 | Falha determinística de build do XML era NAK (redelivery morria em `:e2e_already_used` enganoso) | `{:xml_build_failed, _}` classificada determinística: `PAYMENT_REJECTED` na 1ª entrega com a mensagem REAL da exception | `settlement_service/workers/core_event_processor.ex` |
| 5 | TED enviada não persistia o recebedor (irrecuperável) | Create grava `recipient_name/document/ispb/agency/account` no metadata da transação | `partner_v1/transfers_controller.ex` |
| 6 | Status de PIX e de TED sem contraparte | Bloco aditivo `recipient` (name/document/ispb/institution/agency/account, + key no PIX) lido do metadata | `partner_v1/pix_controller.ex`, `transfers_controller.ex` |
| 7 | Extrato sem contraparte | Campo aditivo `counterparty` por lançamento: `payer_*`/`sender_*` no crédito, `recipient_*` no débito, fallback `counterparty_name` | `partner_v1/accounts_controller.ex` |
| 8 | Comprovante do partner divergente do canônico (sem payer/receiver/pix) | Blocos `payer/receiver/pix/boleto/ted` vêm de `Receipts.Payload.build/5` (o MESMO do IB/admin); chaves antigas preservadas; lançamento legado ganha payer/receiver do metadata | `partner_v1/accounts_controller.ex` |
| 9 | TED recebida do spb_handler grava `sender_*` e o canônico só lia `payer_*` (afetava IB/admin também) | `metadata_party` aceita `sender_*` como alias do lado pagador (+ `institution_name` sem prefixo, só para o remetente); helpers públicos `party_from_metadata/2` e `holder_party/1` | `use_cases/receipts/payload.ex` |
| 10 | Merchant portal `dict_lookup` com o mesmo furo do payer document | Passa o documento do usuário logado | `v2/merchant_portal_controller.ex` |
| 11 | `show_med` mascarava qualquer falha (timeout, NATS fora) como 404 | `not_found` real 404; erro de aplicação 422 (`cabin_app_error`); transporte 502/503/504 (`cabin_error`) | `partner_v1/pix_controller.ex` |
| 12 | `inspect()` de termo Elixir vazando no JSON do parceiro | Mensagens genéricas + detalhe no log (customers create/add_account, webhooks replay, decode BR Code) | `customers_controller.ex`, `webhooks_controller.ex`, `pix_controller.ex` |

Reparo de DADOS em HML (não é código): vínculo `users.partner_id` do user 25100 restaurado; conta 10024216 fundeada com R$ 1,00 (Wallet.deposit, base_units). A conta segue `inactive` (status herdado do ETL; não barra débito, deny-list é blocked/closed).

## 4. Validação

- Partner API core: 132 testes, 0 falhas (pix, transfers, accounts, customers, webhooks, oauth, fees, med, ping, account_events).
- Cabine: dict_service 956/0 (5 skipped), settlement_service 403/0 (2 skipped).
- Suíte completa do core: rodada nesta sessão (resultado no fechamento da sessão).
- Fixtures espelham as formas REAIS do acervo HML (sender_* do spb_handler, payer_* do crédito da ponte, recipient_* do PIX), conforme a regra de nunca validar com fixture de caminho feliz.

## 5. O que fica para depois (handoff)

1. **Deploy**: código pronto SEM deploy (ordem do dono). Precisa de deploy core-api + pix-api (HML primeiro). Sem migrations novas.
2. **Validação viva pós-deploy** (roteiro): como Herbeth, (a) `GET /api/partner/v1/pix/dict/4bf4c483-eaf7-4a8a-be24-f3834c2026eb` deve devolver 200 com titular Qa Lifecycle/KANASTRA e `end_to_end_id`; (b) `POST /pix/payments` de R$ 0,01 com `recipientIspb=49288113` deve construir a pacs.008 (com documento) e seguir ao SPI; se rejeitar, o motivo tem que chegar legível; (c) `GET /transfers/ted` novo deve mostrar `recipient` no status e no extrato; (d) comprovante com payer/receiver.
3. **Webhooks de liquidação sem contraparte** (`pix.payout.*`, `ted.*`): os dispatch sites vivem em `pix_handler.ex`/`spb_handler.ex`, que estavam em edição pela sessão paralela. Adicionar as parties nos payloads depois do merge dela.
4. **`error_reason` não persistido no Core**: a transação do parceiro ficou `rejected` com `error_reason` NULL mesmo com a cabine mandando o motivo (`PAYMENT_REJECTED`/`transaction.error`). O mapeamento vive no `pix_handler.ex` (sessão paralela). Enquanto isso o fix nº 4 já melhora o motivo na origem.
5. **Clientes de teste do Herbeth varridos pelo re-ETL**: só o vínculo do user 25100 foi restaurado. Se ele tinha outros clientes criados via `POST /customers`, precisam ser recriados por ele (a API está de pé de novo).
6. **E2E do PIX-out do parceiro versus consulta DICT**: o send do parceiro gera E2E próprio no Core; a cabine consome o E2E da consulta cacheada quando a chave foi consultada antes. Com o lookup consertado o caminho consulta-depois-paga volta a valer; conferir na validação viva que o E2E da consulta é o que vai na pacs.008 (regra do balde de fichas).

## 6. Gotchas de sessão (para o time)

- O túnel SSM para o Aurora HML em `localhost:15432` SOMBREIA o Postgres de teste local (`monetarie-pg` publica 15432). Sempre `lsof -iTCP:15432` antes de `mix test`; matar o túnel resolve.
- `mix test apps/<app>` na umbrella do PIX não seleciona nada; usar `apps/<app>/test`.
- Suíte do core em paralelo com outra sessão: usar `MIX_TEST_PARTITION` para banco de teste próprio.
