# Handoff — revisao FULL dos builders SPI + camt.060 + verify_spi (2026-06-25)

Sessao dedicada a corrigir a ma formacao das mensagens SPI da cabine PIX e validar
TODAS contra o catalogo oficial do BACEN `v5.12.1`. Tudo abaixo e EMPIRICO: cada
correcao foi validada com `xmllint --schema` contra os XSD oficiais e/ou por `rpc`
na pix-api viva. Regra do dono respeitada: o BACEN e a referencia correta; quando ele
rejeita, o defeito e nosso e foi achado e provado, sem inferencia.

Fonte de verdade: `/Users/luizpenha/mwbank/md/v5.12.1/` (exemplos oficiais + XSD).

## Resultado

- **24/24 builders SPI estruturalmente validos** no XSD oficial v5.12.1 (antes: 8/24).
- **camt.060 builder corrigido e PROVADO** (era a causa-raiz do saldo nunca materializar).
- **verify_spi/verify_dict corrigidos e PROVADOS** (davam `digest_mismatch` ate em
  mensagem que o BACEN aceita).
- Branch: `fix/pix-camt060-verifier-spi-msg-audit` (worktree isolado; NAO mexi no working
  tree da sessao PIX ativa). 3 commits: `ebde1e3f`, `e0bd98ff`, `6f53b2f0`.

## Metodologia de validacao (reusavel)

1. **XML real do builder vivo**: `scratchpad/audit_gen.exs` (rpc) gerou os 24 XML que o
   codigo DEPLOYADO produz e rodou `xmllint --schema` de cada um dentro do container.
2. **Loop local de fix+validacao**: `scratchpad/validate_all.exs` compila os builders do
   WORKTREE com os deps ja compilados do projeto (`ERL_LIBS=pix/backend/_build/dev/lib`),
   gera cada mensagem e roda `xmllint --schema` contra `pix/backend/apps/shared/priv/xsd/
   spi/v5.12.1/*.xsd` (identicos byte-a-byte aos do catalogo). Erro "so `<Sgntr>` vazio" e
   esperado (a assinatura preenche o placeholder) e nao conta como defeito.
3. **Prova end-to-end**: `rpc` na pix-api viva (assinatura real do signer + envio ao BACEN).

## Bug 1 (PROVADO) — camt.060 (consulta de saldo Conta PI)

Dois builders emitiam o mesmo `<RptgReq>` malformado (divergente da sequencia
`ReportingRequest5` do `camt.060.spi.1.9.xsd`): `SpiClient.build_camt060_request/1` (caminho
vivo do `get_balance`) e `MessageBuilder.build_camt060/1`. Quatro violacoes, cada uma provada
pelo xmllint contra o XSD e diff contra `exemplos/camt060/camt.060_SALDO_MOMENTO_msg.xml`:

1. emitia `<Acct>` — NAO existe no perfil SPI (`xmllint`: "Element 'Acct': This element is
   not expected. Expected is one of (Id, ReqdMsgNmId)").
2. `<AcctOwnr>` sem o wrapper `<Agt>` (Party40Choice exige `Agt`).
3. `<ReqdMsgNmId>` DEPOIS de `AcctOwnr` (ordem errada; deve vir antes).
4. faltava `<ReqdBalTp><CdOrPrtry><Prtry>CSA</Prtry></CdOrPrtry></ReqdBalTp>` (o codigo da
   consulta).

Correcao: `<RptgReq>` reescrito 1:1 com os 7 exemplos. `get_balance` passa os 7 tipos por
opts (CSA=saldo/camt.053 default, REL/TRD=lista|arquivo/camt.052, CRE=remuneracao/camt.053,
detalhe=camt.054 + `<Id>`; consultas datadas levam `RptgPrd/FrToDt/Tp=ALLL`) e seta
`message_type=camt.060`. Provado: `xmllint` do corpo corrigido = valido (so resta o
placeholder `<Sgntr>`).

## Bug 2 (PROVADO) — verify_spi / verify_dict (digest da KeyInfo)

`XmlVerifier.verify_spi` retornava `{:error, {:digest_mismatch, :key_info}}` ate para um
pibr.001 que o BACEN ACEITA. Causa-raiz achada lendo o codigo: `XmlSigner.build_key_info`
emite a KeyInfo COM `xmlns:ds` e DIGERE essa forma; mas `build_signature_element` REMOVE o
`xmlns:ds` ao embutir a KeyInfo dentro de `<ds:Signature>` (onde `ds:` ja esta no escopo). O
verificador re-extraia a KeyInfo do corpo (ja SEM `xmlns:ds`) e a canonicalizava sem a
declaracao, gerando um digest diferente. Isso explica tambem por que a saida funciona: a
exc-c14n correta re-adiciona o `xmlns:ds` visivelmente utilizado, batendo com o digest do
signer (e do BACEN).

Correcao: `ensure_ds_namespace/1` re-injeta `xmlns:ds` na KeyInfo extraida antes da exc-c14n,
nos dois fluxos (SPI e DICT). Provado em nivel de digest E end-to-end (rpc na task viva):
- digest recomputado com o fix == `DigestValue` do SignedInfo (`d/pK6...`); o codigo antigo
  dava outro (`AdSV...`).
- `verify_spi(assinado)` => **`{:ok, :valid}`** em camt.060 e pibr.001 reais (3 digests +
  assinatura OK); o verificador deployado (sem fix) ainda da `{:digest_mismatch, :key_info}`.

## Auditoria das 24 mensagens (cobertura TOTAL)

Legenda: ENV = nos enviamos; REC = recebemos (builder usado pelo simulador). Status apos as
correcoes desta sessao.

| Mensagem | Fluxo | Estava | Defeito estrutural | Status |
|---|---|---|---|---|
| pacs.008 | ENV | OK | — | valido |
| pacs.002 | ENV | OK | — | valido |
| pacs.004 | ENV | OK | — | valido |
| pibr.001 | ENV | OK | — (referencia de "certo") | valido |
| pibr.002 | REC | OK | — | valido |
| camt.054 | REC | OK | — | valido |
| pain.013 | ENV | OK | — (a "falha" do MndtId era artefato de param de teste) | valido |
| pain.014 | REC | OK | — | valido |
| trck.002 | ENV | OK | — | valido |
| camt.014 | REC | OK estrut. | — (so o valor do enum PtyRole era invalido no param) | valido |
| **camt.060** | ENV | MALFORMADO | `<Acct>` inexistente; `AcctOwnr` sem `Agt`; `ReqdMsgNmId` fora de ordem; sem `ReqdBalTp` | **CORRIGIDO** |
| **reda.014** | ENV | MALFORMADO | faltava um nivel `<Id>` (`PtyId>Id>Id>PrtryId`) | **CORRIGIDO** |
| **reda.022** | ENV | MALFORMADO | idem `<Id>`; `<TechAdr>` exige elemento aninhado, nao texto | **CORRIGIDO** |
| **reda.031** | ENV | MALFORMADO | idem `<Id>` (`SysPtyId>Id>Id>PrtryId`) | **CORRIGIDO** |
| **camt.025** | REC | MALFORMADO | `<GrpHdr>`→`<MsgHdr>`; `OrgnlMsgId/MsgId` + `OrgnlPmtId/PrtryId` + `ReqHdlg/Sts/Cd` | **CORRIGIDO** |
| **camt.029** | ENV | MALFORMADO | faltava `Assgnr/Assgne`; `Conf` so aceita `INFO` (era `ACDA`); `CxlDtls/OrgnlPmtInfAndSts`; falta `SplmtryData` | **CORRIGIDO** |
| **camt.052** | REC | MALFORMADO | `<Rpt>` sem `CreDtTm`; faltava `TxsSummry` + `AddtlRptInf` | **CORRIGIDO** |
| **camt.053** | REC | MALFORMADO | `<Stmt>` sem `CreDtTm/FrToDt`; faltava `<Bal>` (SADP/SABK/REMN...) | **CORRIGIDO** |
| **camt.055** | ENV | MALFORMADO | faltava `Assgnr/Assgne`; `OrgnlPmtInfAndCxl` (era `OrgnlGrpInfAndCxl`); `CxlRsnInf/Rsn/Prtry` (era `Rsn/Cd`); `TxInf/SplmtryData` | **CORRIGIDO** |
| **pain.009** | ENV | MALFORMADO | estrutura SEPA generica trocada pela `Mandate19` do PIX; `SplmtryData` exige EXATAMENTE 3 `MndtPrcgDtls` | **CORRIGIDO** |
| **admi.004** | REC | MALFORMADO | raiz `<SysEvtAck>` (correto = `<SysEvtNtfctn>/EvtInf/EvtCd/EvtDesc`) | **CORRIGIDO** |
| **admi.002** | REC | MALFORMADO | raiz `<SysEvtNtfctn>` (correto = `<admi.002.001.01>` RltdRef + Rsn) | **CORRIGIDO** |
| **pain.011** | ENV | MALFORMADO GRAVE | SEMANTICA TROCADA: emitia `<MndtAmdmntReq>` (pain.010, inexistente no SPI); correto = `<MndtCxlReq>` (MandateCancellation) | **CORRIGIDO** |
| **pain.012** | ENV | MALFORMADO GRAVE | SEMANTICA TROCADA: emitia `<MndtCxlReq>`; correto = `<MndtAccptncRpt>` (MandateAcceptanceReport) | **CORRIGIDO** |

Resumo: 14 builders estavam malformados (10 deles em mensagens que ENVIAMOS), 2 com
semantica de mensagem TROCADA (pain.011/012). Todos corrigidos e validados no XSD.

## camt.060 vs BACEN homolog — ABERTO, documentado SEM overclaim

Enviei a camt.060 corrigida (XSD-valida + assinatura provadamente valida pelo verify_spi
corrigido) ao BACEN homolog, nos DOIS canais. O BACEN devolveu `{:ok, %{}}` (aceite na ICOM)
e, ~1s depois, um **admi.002** assincrono recusando:

```
<admi.002.001.01>
  <RltdRef><Ref>RUEkBnvwhPu0m0cp7ufRE16zt6wwAohnj</Ref></RltdRef>
  <Rsn>
    <RjctgPtyRsn>Erro no processamento da ICOM</RjctgPtyRsn>
    <RjctnDtTm>2026-06-25T00:14:57.805Z</RjctnDtTm>
    <RsnDesc>Schema desconhecido, nao habilitado para uso ou canal incorreto</RsnDesc>
  </Rsn>
</admi.002.001.01>
```

ELIMINADOS COM PROVA (nao por exclusao): estrutura (XSD-valida), assinatura (verify_spi
corrigido confirma valida), canal (camt.060 e pibr.001 roteiam ambos para `:secondary` —
provado empiricamente; testei tambem `:primary`), transporte/endpoint (pibr.001 funciona no
MESMO `/api/v1/in/{ispb}/msgs`).

Fato novo decisivo: o `RltdRef/Ref` do admi.002 e `RUEkBnvwhPu0m0cp7ufRE16zt6wwAohnj` — um id
de TRANSPORTE da ICOM (PI-MessageId), NAO o nosso `BizMsgIdr` (`M46026562...`). A recusa
"Erro no processamento da ICOM" ocorre na camada de INGESTAO, ANTES da validacao do corpo de
negocio. Ou seja, esta alem da construcao da nossa mensagem.

Hipotese residual (NAO provada, exige confirmacao do BACEN, NAO declarar como conclusao):
`camt.060.spi.1.9`/Conta PI nao habilitado para o ISPB `46026562` no homolog, OU exigencia de
canal/endpoint especifico nao documentado. **NAO declarar camt.060 OK ate um camt.053 real
chegar e materializar.** Proximo passo recomendado: cobrar do BACEN a habilitacao do schema
camt.060 / Conta PI para o ISPB no homolog (com a evidencia acima de que a mensagem ja esta
correta do nosso lado).

## Deploy

NADA foi deployado. As correcoes estao no worktree isolado `fix/pix-camt060-verifier-spi-msg-audit`
(3 commits). A sessao PIX ativa builda do working tree principal; coordenar antes de mergear
e buildar a pix-api. Arquivos alterados (todos compilam e validam):
- `apps/shared/lib/shared/crypto/xml_verifier.ex` (verify_spi/verify_dict)
- `apps/shared/lib/shared/bacen/spi_client.ex` (camt.060 caminho vivo)
- `apps/shared/lib/shared/bacen/iso20022/message_builder.ex` (os 24 builders)
