# Migração da abertura de infração DICT para o fluxo vigente (API 2.12.1) - validação viva em HML

Data: 2026-07-18 (noite 3). Frente P0 do handoff `2026-07-18-noite2-partner-api-100-e-pendencias-handoff.md`, executada em sessão dedicada com TDD e revisão adversarial dupla (spec + qualidade) em cada task. Plano: `docs/plans/2026-07-18-migracao-infracao-dict-212-plan.md`.

## O que o BACEN mudou (provado na spec oficial)

- `POST /infraction-reports/` (criar notificação de infração) e `PUT /infraction-reports/{id}` foram marcados deprecated na **2.11.0_RC1 de 2026-05-04** (changelog oficial da API-DICT); homolog responde 410 Gone desde 2026-04-13. NÃO existe path novo de criar infração.
- Fluxo vigente (2.12.1): o PSP pagador cria **Recuperação de Valores** (`POST /funds-recoveries/`) com `RootTransactionId` + `SituationType` + `ContactInformation` (Email+Phone) OBRIGATÓRIA + `TrackingGraphParameters` opcional limitado (máx 5 transações, 2 hops, PT2H); as notificações de infração nascem dentro da recuperação (`Reason` = REFUND_REQUEST/REFUND_CANCELLED). Marcação do fraudador: `POST /fraud-markers/`. Seguem vigentes: list/get/acknowledge/cancel/close de infração.

## Defeitos NOSSOS achados e corrigidos (além do endpoint morto)

1. Builder de funds-recovery emitia `TrackingGraphParameters` SEMPRE com defaults 0/500/PT24H/5, todos fora dos limites 2.12: causa provável do `FundsRecoveryInvalid` do MED open de 18/07 (confirmado na revalidação: com o shape novo o erro virou recusa de NEGÓCIO, não de shape).
2. `ContactInformation` só era enviada se presente, e nenhum elo threadava contato: obrigatória na 2.12. Contato institucional agora vem de `DICT_MED_CONTACT_EMAIL`/`DICT_MED_CONTACT_PHONE` (valores do dono; fail-closed `med_contact_not_configured` sem eles).
3. Constraint de dedup do espelho de infração era decorativa (índice inexistente): retry duplicava espelho + webhook. Migration `20260718200000` criou o índice único parcial real.
4. Enum de `situation_type` local não aceitava COERCION/UNKNOWN (recovery irrevogável no BACEN + rollback local); estendido ao SituationType 2.12 completo, com UNKNOWN rejeitado na ESCRITA (valor de leitura do acervo v1 pela spec).
5. Erros em tupla estouravam `Protocol.UndefinedError` (500/mensagem de exceção ao parceiro) no responder NATS e controllers; cláusulas legíveis nas 3 superfícies.

## O que mudou no código (contrato do parceiro INALTERADO)

- `DictService.Infractions.create_infraction_report/1` em modo bacen cria/REUSA a Recuperação de Valores (1 por transação, convergente com `/pix/med`; reuso inclusive de cancelada, o BACEN recusa duplicata para sempre) e vincula o espelho local pelo Id BACEN; evento `INFRACTION_REPORTED` preservado na mesma transação.
- `cancel_report` de espelho vinculado cancela a RECOVERY (o endpoint de cancel de infração recusa vinculadas) com cura idempotente (recovery já CANCELLED cura o espelho sem ida ao BACEN; espelhos-irmãos convergem).
- Caminho morto removido inteiro: client/builder/parser/adapters do create; enum `CREATE_INFRACTION_REPORT` mantido só para leitura de acervo (reconciler despacha por ele).
- Suítes: dict_service 461/0 (multi-seed), shared 1813/0, core partner 28/0 SEM tocar código do core, gate de paridade docs 66 rotas/42 eventos verde.

## Deploy HML (autorizado pelo dono)

- DDL `20260718200000` aplicada via rpc na task VELHA antes do swap; `migrate()` da imagem nova provado no-op ("Migrations already up").
- pix-api HML **:191** (rollback :190), imagem `homolog-56d4545e-infracao212-20260718`, digest `sha256:2ebb6532c9c6874d9a96603c9248ae2db42b4288b8bfdb93e927e2ee1396aea4`; envs de contato na task-def.
- Pós-swap: health 200, ICOM CPM "resuming 4 existing open sessions" + CSM idem (zero janela surda).

## Validação viva (prova BACEN)

Com a partner key da campanha, sobre o PIX real de hoje `E4602656220260718221400000000655` (rejeitado pelo PSP recebedor de homolog):

- `POST /pix/infractions` e `POST /pix/med` chegaram ao BACEN VIVO pelo endpoint VIGENTE e voltaram **400 `FundsRecoveryTransactionNotIndexed`** (problem+xml assinado SERPRO; correlationIds `A202607182047021564602656276B0FD` e `A20260718204702506460265628A3F8F`): "The root transaction for recovering funds is not indexed".
- Leitura: o **410 Gone MORREU**; o shape 2.12 foi ACEITO; a recusa é regra de negócio verdadeira (raiz rejeitada, nunca liquidada, logo não indexada no DICT). Ao parceiro, 422 legível `bacen_rejected`.
- Fail-closed provado: após as recusas, `GET /pix/infractions` e `GET /pix/med` = 0 itens (nenhum espelho espúrio).

**Limitação honesta**: o degrau final (recuperação ACEITA sobre raiz LIQUIDADA) não é alcançável em HML hoje, porque o PSP recebedor de homolog rejeita todo PIX-out nosso (não existe transação liquidada com a Monetarie como PSP pagador). Primeira oportunidade real: as sequências do cliente de segunda 21/07 (abrir infração sobre transação de cliente = decisão do dono).

## Follow-ups registrados

- `POST /webhooks/{id}/test` do partner faz broadcast do evento `webhook.test` aos INSCRITOS nesse evento em vez de entregar ao webhook testado (parceiro que não assina webhook.test não recebe o próprio teste). Achado na verificação de HMAC do relatório do cliente.
- MED sem `details` vira 503 `bacen_external_failed` em vez de 4xx local (pré-existente; legibilidade).
- Contrato de retry do create: 422 de changeset em vez de `already_reported`/sucesso idempotente (polimento).
- Mapear COERCION no FraudMarkerEnumMapper (decisão de produto; hoje fail-closed `unknown_fraud_type`).
- Sweep do anti-padrão `put_env(nil)` em restore de testes (1 arquivo corrigido, ao menos 1 outro existe).
- Menores: regex decimal compartilhada, índice em `original_end_to_end_id`, fonte única do enum SituationType, warnings pré-existentes do responder, lookup por `desc: id` num flip-flop de modo bacen/local.

## PRD

NÃO deployado (aguarda OK explícito do dono; janela nunca durante operação de money-path). Receita: mesma imagem por digest via imagetools, DDL via rpc antes do swap, envs de contato na task-def de PRD.
