# Trilha 02: PIX-in money path completo, AVIV (two-phase ACSP para ACCC via OnZ) vs Monetarie (BACEN liquida antes da pacs.008)

Data: 2026-07-09. Trilha READ-ONLY do mandato de mapeamento exaustivo AVIV/coreproviders vs cabine Monetarie.

Metodo: leitura de codigo com evidencia arquivo:linha nos dois repositorios. Nenhuma chamada AWS, nenhuma execucao de runtime. Onde a afirmacao vem de documento (handoff, CLAUDE.md) e nao do codigo, isso esta dito explicitamente. Onde nao foi possivel provar, esta marcado NAO VERIFICADO.

Repositorios:

- AVIV: `/Users/luizpenha/coreproviders` (branch aviv-hml, OTP app `:fluxiq`)
- Monetarie: `/Users/luizpenha/monetarie` (origin/main `76b1dc17`)

---

## 1. Como a AVIV faz (fluxo passo a passo)

A AVIV e participante INDIRETO. A mensageria SPI chega por long-poll HTTP na OnZ CloudPIX (porta 80, VPC peering, sem TLS; contexto do mandato). O dinheiro so entra no TigerBeetle depois que a OnZ entrega a pacs.002 ACCC (liquidacao confirmada). Esse e o motivo de existir o two-phase.

### 1.1 Recepcao: Poller de long-poll com slots Redis

- `backend/lib/fluxiq/services/pix_providers/onz/poller.ex:1-10`: GenServer de long-poll com slots SETNX no Redis limitando os pollers ativos; standby assume quando um lease expira.
- Teto regulatorio: no maximo 6 threads LP simultaneas por participante (SPI 2.2.2.10), guard de boot em `backend/config/runtime.exs` (bloco `onz_lp_max_pollers`, linhas ~1076-1080).
- `poller.ex:60` (`@receive_timeout 10_000`): timeout do GET calibrado empiricamente (worst case observado 7061ms). Historico de regressao documentado no proprio codigo (linhas 41-59): reduzir o timeout causou cascata de 401 e cascata ACSP 422.
- `poller.ex:109-110`: ciclo de agendamento absoluto `cycle_ms=5000` com `stagger=833ms` por slot (6 slots cobrem o ciclo); a janela morta de 833ms foi medida e 16,7% das mensagens caiam nela (comentario nas linhas 94-104).
- Pipelining (Poller V2): `poller.ex:214-248`, o GET roda em `Task.start` fora do GenServer; o mailbox continua processando heartbeat e slot management durante o hold de 3-5s da OnZ. Antes (sincrono) o throughput era ~72 msg/min com 6 slots e 37/100 PIX-in dropavam em concorrencia 1 (comentario linhas 222-226).
- Cursor e o ACK: `poller.ex:747-752`, o `next_token` devolvido no header `ONZ-Pull-Next` E o cursor de ACK; o proximo GET com esse cursor confirma a entrega. Se o cursor nao avanca, a OnZ reentrega a mesma mensagem ate ~5 vezes.
- Latencia fim a fim medida (comentario `poller.ex:106-108`): P50 471ms, P95 561ms, P99 737ms (PRD 2026-04-20, 1256 tx em 60 min). P50 dentro da meta BACEN de 500ms; P95/P99 acima.

### 1.2 Persistencia durance antes do ACK: dois modos de consumo

- `poller.ex:738-757`: com `ONZ_LP_INBOX_CONSUMER=processor`, o poll fiber persiste cada mensagem na tabela duravel `onz_lp_inbox` e enfileira um job Oban por mensagem ANTES de avancar o cursor (`process_async`, `poller.ex:1307-1351`; `async_persist_one`, `poller.ex:1357-1395`). Falha transiente de DB = cursor NAO avanca = OnZ reentrega (persist idempotente). Frame estruturalmente invalido = DLQ + ACK (nunca trava o cursor).
- Default do CODIGO e `:sync` (`backend/config/runtime.exs:1054-1060`: `System.get_env("ONZ_LP_INBOX_CONSUMER", "sync")`). Os comentarios do codigo afirmam que `:processor` e o default operacional em HML/PRD (`poller.ex:739`), mas isso vem de env var, nao do default do codigo. NAO VERIFICADO em runtime (regra desta trilha: sem AWS).
- Modo `:sync` (fallback legado): `process_sync` (`poller.ex:1253-1294`) roda o money path inline no poll fiber e so avanca o cursor com `:ok` (contrato WAL).
- Consumo assincrono: `backend/lib/fluxiq/workers/onz_lp_inbox_processor.ex:48-51` (Oban queue `:onz_lp_process`, `max_attempts: 10`). O worker faz claim atomico da row (`mark_processing`, estados `received|processing|failed`; terminal `processed|poisoned` nunca re-executa, linhas 177-203) e REUSA o entrypoint identico `Poller.process_inbound_message/5` (linhas 205-219).
- Alternativa node-local (`LpLocalProcessor`) atras da flag `ONZ_LP_LOCAL_PROCESSOR` (default false, `runtime.exs:1069`): processa no pod que recebeu para eliminar a corrida cross-AZ do SKIP LOCKED (o no da AZ do writer Aurora abocanhava ~96% da fila Oban compartilhada, comentario `runtime.exs:1064-1068`).

### 1.3 Dedup e poison policy

- Dedup durable: `backend/lib/fluxiq/infra/dedup/message_dedup.ex:37` (`try_claim/3` atomico: INSERT em `processed_messages` no PG primeiro, ETS como cache depois; so um caller ganha o INSERT). Chamado por mensagem em `poller.ex:959-966`. Duplicata = `:ok` (cursor avanca, sem reprocesso, `poller.ex:967-974`). Falha da infra de dedup = DLQ manual + `:ok` (avanca; alternativa de retry arriscaria loop infinito, `poller.ex:1052-1073`).
- Poison policy (P0-D): `poller.ex:31-39` e `poller.ex:1079-1103`. Contador de falhas consecutivas por message_id no Redis (TTL 1h, threshold default 5, `ONZ_POISON_MAX_RETRIES`); no threshold a mensagem vai para DLQ + row `poisoned` + alerta CRITICO, e retorna `:ok` para o cursor avancar (retry limitado, nunca drop silencioso).

### 1.4 Roteamento do inbound

`dispatch_inbound` (`poller.ex:1658-1726`) roteia o payload ja mapeado (`Mapper.from_inbound`, `backend/lib/fluxiq/services/pix_providers/onz/mapper.ex:224`):

1. pacs.004 recebida (contraparte devolvendo para nos) -> `ReturnInHandler.handle` (linha 1664-1666).
2. pacs.002 ACCC/RJCT de devolucao inbound pendente (chave Redis `{return_pending}`) -> `ReturnInHandler.confirm_return` (1670-1674).
3. pacs.002 ACCC para PIX-in pendente (chave Redis `pix_in_pending:<e2e>`, `has_pending_pix_in?` em 1846-1855) -> `TbFirst.Handler.handle_accc` (1679-1680). Tipos candidatos: `@accc_candidate_types ["completed", "settled"]` (1644).
4. pacs.002 RJCT para PIX-in pendente -> `TbFirst.Handler.handle_rjct` (1682-1683). `@rjct_candidate_types ["rejected", "failed"]` (1645).
5. pacs.002 de outbound_request registrada (PIX-out, TED, devolucao MED) -> `AtomicPaymentHandler.maybe_handle` (1691-1695), com fallback D-prefix para devolucao orfa (1733-1750).
6. Fallback: pacs.008 (`type="created"`) e tipos desconhecidos -> `TbFirst.Handler.handle` = Fase 1 do two-phase (1697-1701).

Reroute anti-colisao: quando a pacs.002 confirma uma pacs.004 NOSSA (refund-out), `maybe_reroute_refund_out_e2e` troca o `end_to_end_id` para o `OrgnlInstrId` para nao cair no handler de PIX-in (1899-1918).

### 1.5 Fase 1 (pacs.008 recebida): validar, responder ACSP, guardar intent, SEM deposito

`backend/lib/fluxiq/use_cases/pix/tb_first/handler.ex`:

- Doc do desenho: linhas 2-19. Fase 1 = valida, manda pacs.002 ACSP para a OnZ, guarda intent pendente no Redis, retorna `:ok`. "This prevents phantom credits: money only enters TB after SPI settlement is confirmed" (linha 18).
- Pipeline de validacao com short-circuit: parse E2E (61-71, formato invalido = RJCT BE01), resolucao de conta (`AccountResolver.resolve`, 75-93, rejeicao = RJCT com codigo BACEN), limite PIX-in (`LimitCheck.verify`, 106-122, estouro = RJCT AM02), `AcceptanceValidator` (QR ja pago/cancelado/expirado, 124 e 183-198), `ReceivingBlacklistValidator` (126-181), `CnpjReceivingValidator` (134-164). Toda rejeicao envia pacs.002 RJCT inline (com retry Oban `Pacs002RetryWorker` se o envio falhar, 1138-1187) e persiste em `failed_transactions` para metrica de taxa de sucesso (1038-1072).
- Aceite: `send_acsp_and_store_pending` (218-328). Ordem: SET no Redis `pix_in_pending:<e2e>` com TTL 600s (241-247) -> pacs.002 ACSP inline via adapter OnZ (249, 1100-1136) -> WAL duravel Fase 1 `acsp_sent` em `pending_pix_in_deposits` (259-267 e 336-391; fail-open, nunca aborta o hot path). Racional do WAL: pod crash com TTL do Redis expirado antes do ACCC deixaria orfao um PIX-in JA liquidado no BACEN (256-258).
- ACSP rejeitado pela OnZ (422/timeout): `acsp_rejected_with_mgmt_fallback` (418-534) consulta imediatamente a verdade em `GET /v3/icom/mgmt/{e2e}`: `CONCLUIDA` = deposita direto; terminal rejeitada = nao deposita; inconclusivo = guarda pending para o ACCC. Decisao pura testavel em 536-549.
- Redis fora antes do ACSP: fallback de deposito imediato (293-327 e 856-880).

### 1.6 Fase 2 (pacs.002 ACCC): deposito TB + espelho PG + webhook

- `handle_accc` (555-591): GETDEL atomico via script Lua no Redis (41, 566). Sem pending (TTL expirou, Redis caiu, ACSP falhou) -> fallback pela verdade OnZ MGMT, deposito fail-closed somente com status `CONCLUIDA` (693-734).
- `execute_deposit_from_pending` (593-691), a espinha do caminho do dinheiro:
  1. WAL row `pending` ANTES de TB e Oban (627-656, fail-open com telemetria).
  2. Oban-first: enfileira `PixInPgWriteJob` modo `full_pipeline` ANTES do deposito TB (658-667). Se o pod morrer entre o enqueue e o TB, o job Oban roda o pipeline inteiro. O job e agendado com delay de 30s + preflight no PG para nao competir com o caminho inline (`backend/lib/fluxiq/workers/pix_in_pg_write_job.ex:27,35-56`).
  3. Deposito TB: `Deposit.execute` (673).
  4. `handle_deposit_result` -> `publish_success` -> `inline_pg_write` (886-1013): PgWriter inline para webhook imediato (~0ms vs ~500ms do poll do Oban); sucesso = `pg_done` + DELETE da row WAL (936-941); falha = row fica `pending` e o recovery re-executa em 2-5min (943-949).
- Deposito TB e um batch LINKED de 3-4+ transfers com segregacao COSIF (`backend/lib/fluxiq/use_cases/pix/tb_first/deposit.ex:1-21,54-104`): T1 moeda eletronica -> SPI (funding primeiro, senao `:exceeds_credits`), T2 SPI -> transito PIX-in, T3 transito -> wallet do cliente, T4 opcional wallet -> conta de tarifa (fee clampada `min(fee, amount - 1) |> max(0)`, linha 17 e 255-287), mais pernas de split de tarifa e espelho caixa (107-133). IDs deterministicos `BatchChain.root_id` (SHA-256 da chave de idempotencia): replay produz os MESMOS ids, TB devolve `:exists`, e o `ErrorClassifier` trata como sucesso idempotente (180-195).
- Coalescer opcional de depositos TB: entrypoint `create_transfers_for_deposit` roteia pelo `DepositCoalescer` quando `TB_DEPOSIT_COALESCE_ENABLED=true` (default false) (`backend/lib/fluxiq/infra/tigerbeetle.ex:600-618`; processo sobe por default no supervisor, `backend/lib/fluxiq/application.ex:115-118`).
- Espelho PG: `backend/lib/fluxiq/use_cases/pix/tb_first/pg_writer.ex`. MERGE idempotente via stored procedure `upsert_pix_in_transaction` com retry 3x + backoff (277-304). Timestamp autoritativo = commit timestamp do TB (o unico ancora temporal estavel em retry, garante PK composta `(id, started_at)` identica em todo replay; 71-88). MERGE falhou = retorna erro para NAK/retry, "TB HAS THE MONEY, MANUAL RECONCILIATION REQUIRED" logado (127). Side effects (webhook `pix.charge.paid`, link do QR, gravacao da tarifa em `fee_transactions`) so rodam DEPOIS do MERGE confirmado (136-145, 155-274). O job Oban de seguranca roda com `skip_side_effects: true` para nao duplicar webhook (corrigiu corrida com 28,6% de webhooks duplicados; `pix_in_pg_write_job.ex:144-148`).
- RJCT depois do ACSP: `handle_rjct` (826-850) remove o pending e NAO deposita; persiste `RJCT_AFTER_ACSP` em `failed_transactions`.

### 1.7 Redes de seguranca (o que acontece se o ACCC nunca chega ou algo morre no meio)

Camadas, todas vivas no cron Oban (`backend/config/runtime.exs:650-730`):

1. `PixInRecoveryWorker` a cada 5min (`runtime.exs:668`): varre `pending_pix_in_deposits` em `(acsp_sent, pending, tb_done)` paradas ha mais de 2min e re-executa a Fase 2 idempotente; apos 10 tentativas marca `failed` para revisao manual (`backend/lib/fluxiq/workers/pix_in_recovery_worker.ex:1-24,65-101`). Ciclo de estados do WAL: `acsp_sent -> pending -> tb_done -> pg_done (DELETE) | failed | poisoned` (`backend/lib/fluxiq/schemas/pix/pending_pix_in_deposit.ex:11-26`).
2. `OnzLpInboxRecovery` a cada 1min (`runtime.exs:712`): rows `processing` presas >5min voltam para `received` e re-enfileiram; `failed` abaixo do budget re-enfileira; acima vira `poisoned` + alerta (`backend/lib/fluxiq/workers/onz_lp_inbox_recovery.ex:11-31`).
3. `PixInOrphanReconciliation` a cada 15min (`runtime.exs:690`): compara CONCLUIDA na OnZ (verdade BACEN) vs TigerBeetle e auto-remedia orfaos (deposito com limite `@auto_deposit_limit 500` por ciclo), lookback real de 30 dias, advisory lock global por cluster (`backend/lib/fluxiq/workers/pix_in_orphan_reconciliation.ex:1-47`).
4. `PostDeployReconciliation` como rede final citada no proprio poller para a cascata ACSP 422 (`poller.ex:56-59`).
5. Monitores dedicados: `PixInOkRateMonitor`, `OnzLagMonitor`, `OnzPollerLivenessMonitor`, `OnzDlqDepthMonitor` (profundidade de `nats_dead_letters` nao-replayed), `OnzLpInboxDepthMonitor` (`runtime.exs:712-719`).

### 1.8 Unidades e tarifas

- Subcentavo: 1 BRL = 10.000 unidades. Evidencia: `parse_mgmt_amount` converte valor em reais com `round(f * 10_000)` (`handler.ex:810-819`).
- Tarifa PIX-in calculada no proprio caminho de deposito (`FeeCalculator.calculate` com clamp, `deposit.ex:255-287`), cobrada como perna T4 do MESMO batch linked do TB e registrada no PG depois do MERGE (`pg_writer.ex:209-239`, nao bloqueante).

---

## 2. Como a Monetarie faz (fluxo passo a passo)

A Monetarie e participante DIRETO (ISPB 46026562). O modelo canonico provado em prod (2026-07-09): o BACEN liquida ANTES de entregar a pacs.008 e o recebedor NAO recebe pacs.002 de volta (`docs/handoff/2026-07-09-incidente-pacs008-canonico-pixin-handoff.md:20-23`; comentario no codigo `pix/backend/apps/spi_service/lib/spi_service/workers/inbound_processor.ex:947-951`). Portanto nao existe two-phase: quando a pacs.008 chega, o dinheiro JA e nosso.

### 2.1 Recepcao ICOM: GET direto no BACEN com ACK condicionado a persistencia

- Worker CPM de long-poll ICOM: `pix/backend/apps/spi_service/lib/spi_service/icom/cpm/worker.ex`. Invariante central: o proximo GET (que para o BACEN e o ACK do lote anterior) NUNCA sai antes de `AckTracker.persist_batch/2` retornar `{:ok, _}` (linhas 36-37 e 55-56). Queda de Aurora = backoff exponencial com jitter `2^n * 100ms` cap 30s (42, 79-81), sem ACK, BACEN reentrega.
- Ponte para NATS: `pix/backend/apps/spi_service/lib/spi_service/icom/nats_bridge.ex:1-44`. Cada parte do multipart vira um publish em `monetarie.spi.inbound.<msg_type>` com payload `{message_type, message_id, xml_content}`. Recovery na (re)subida: `republish_pending/1` drena rows com `nats_published_at IS NULL` (24-30, 71-96).
- Latencia da cabine no ICOM: 24-28ms por GET vs long-poll OnZ p50 471ms (afirmacao do comparativo `docs/reports/2026-07-04-comparativo-aviv-coreproviders-monetarie.pdf`, citado no contexto do mandato; nao re-medido nesta trilha).

### 2.2 InboundProcessor: gates fail-closed antes de qualquer efeito

`pix/backend/apps/spi_service/lib/spi_service/workers/inbound_processor.ex`:

- Consumidor JetStream durable: stream `MONETARIE_SPI`, filter `monetarie.spi.inbound.>`, `batch_size: 100`, `max_concurrency: 100` (79-85). ACK/NAK e poison policy no `Shared.Workers.BaseWorker` (`pix/backend/apps/shared/lib/shared/workers/base_worker.ex:309` `max_deliver`, 525-546 `nak_or_poison`: abaixo do limiar NAK com backoff, acima publica durably em `monetarie.dlq.poison.<consumer>`).
- Gates de entrada (180-181): XMLDSig verify fail-CLOSED de toda mensagem BACEN com `xml_content` (`maybe_signature_check`, 309-354) e validacao XSD fail-CLOSED para pacs.008 (`maybe_validation_check_pacs008`, 272-296).
- Dedup em 2 camadas: Redis SET NX TTL 1h (fail-open, 119-158) + advisory lock/existencia no proprio `Repo.transaction` (F2, 888-902): duplicata detectada dentro da transacao = rollback limpo, ACK, sem transacao duplicada.

### 2.3 Decisao pura do pacs.008 (pos-incidente 2026-07-09)

`process_incoming_payment` (404-513):

- Resolucao do ISPB credor pela cadeia mensagem -> XML AppHdr To -> nosso ISPB (nunca crasha; antes o pacs.008 real crashava sem resposta e o BACEN devolvia AB03 por timeout; 411-426).
- Ordem com short-circuit e decisao PURA `inbound_outcome/4` (432-587): SPI validation -> sancoes -> validacao da conta credora no Core (RPC NATS `monetarie.core.pix.validate_account`, timeout 200ms; 111-116, 971+) -> gate de QR (txid de cobranca; QR terminal/expirado/valor divergente nunca vira credito; 452-463, 626-647). Isso eliminou o phantom credit de 2026-06-07 em que uma mensagem rejeitada AINDA caia no caminho de credito e gerava dupla pacs.002 (432-441).
- Indisponibilidade TRANSITORIA da validacao de conta = `{:retry, reason}`: NAO emite pacs.002, NAO persiste rejeicao, devolve erro para NAK e reentrega JetStream; um veredito definitivo do Core e que rejeita (495-511, 540-551). Este e um dos 5 defeitos do incidente 2026-07-09 (antes, indisponibilidade nossa virava rejeicao indevida com codigo invalido).
- Rejeicao por conta invalida/bloqueada/encerrada (AC03/AC06/AC07/AC14/AG03): o credito ja liquidado pelo BACEN nao pode ficar conosco, entao vira DEVOLUCAO automatica pacs.004 ao pagador (481-493, 705-717); as demais rejeicoes seguem em pacs.002 RJCT.

### 2.4 Caminho de aceite: credito PI + evento ao Core + ACSP + ACCC

`do_process_validated_payment` (860-968), na ordem:

1. `Repo.transaction` atomica: upsert em `bacen_inbound` + INSERT da linha de negocio em `monetarie_spi.messages` com `status_id` PDNG (866-909, 2707-2736). Falha = rollback + NAK.
2. Credito na Conta PI (espelho de liquidez do participante): `update_pi_balance(:credit, ...)` (917-918, 2914-2945).
3. Evento ao Core pelo OUTBOX duravel: `publish_transaction_event(pacs008_event_type(), ...)` publica `monetarie.spi.transaction.created` (flag `two_phase_pix_in_enabled` default false, 2858-2864) via `Shared.Nats.Publisher.publish_async` DENTRO de `Repo.transaction` (job Oban atomico com o estado; 2866-2894; padrao outbox em `pix/backend/apps/shared/lib/shared/nats/publisher.ex:1-45`). Comentario de contrato no proprio call site: "O credito ao cooperado vai por ESTE evento (outbox duravel Oban). So depois dele garantido e que a resposta de aceite ao BACEN e enfileirada" (928-930).
4. Resposta pacs.002 ACSP ao BACEN: `send_pacs002_response(message, "ACSP")` (933-938). O build passa pelo GUARDA de codigo `RejectCodes.sanitize_pacs002/1` (2575-2579, 2644-2660): nenhum `Cd` fora do enum `ExternalStatusReason1Code` sai; codigo invalido vira ED05 + log CRITICO. Fonte unica do enum com 44 codigos extraidos do XSD oficial v5.12.1: `pix/backend/apps/shared/lib/shared/bacen/iso20022/reject_codes.ex:1-46`. E2E recuperado do XML quando o envelope NATS nao traz (2569-2585, outro defeito do incidente: pacs.002 saia com OrgnlEndToEndId vazio = XSD-invalida = nao entregue). O publish do pacs.002 tambem e outbox atomico (2611-2618).
5. Link do QR pago (fail-soft, nunca desfaz o pagamento aceito; 940-945, 649-688).
6. Avanco da linha para ACCC: como entrada nao recebe pacs.002 de volta, a propria cabine avanca PDNG -> ACCC apos credito + ACSP (947-952, 2470-2493; fail-soft).

O envio fisico da pacs.002 e feito pelo `OutboundSender` (consumidor de `monetarie.spi.outbound.>`): assinatura XMLDSig fail-CLOSED via HSM (mensagem sem assinatura NUNCA vai ao BACEN) + gate XSD do XML assinado ANTES do POST mTLS (`pix/backend/apps/spi_service/lib/spi_service/workers/outbound_sender.ex:85-102,168-178,265-271`).

### 2.5 pacs.002 RECEBIDA (fail-CLOSED, pix-api:20)

Nao se aplica ao PIX-in (canonico: recebedor nao recebe pacs.002), mas e a perna de liquidacao do PIX-out e foi endurecida no mesmo deploy:

- `process_status_report` (1770-1845): correlacao multi-estrategia (RtrId de devolucao -> E2E -> original_message_id/resource_id -> hop via `bacen_outbound`; 3108-3114), mapa `map_pacs002_status` com ACCC/ACSC/STLD -> "settled" (1875-1881), guarda de estado terminal contra pacs.002 fora de ordem (3023-3033, 3088-3094).
- Fail-CLOSED: erro de lookup ou de publish propaga `{:error, ...}` ate o BaseWorker (NAK, retry limitado, DLQ); antes a excecao era engolida com warning e o settle/reject se perdia em silencio (3005-3016, 3063-3085; confirmado no handoff `docs/handoff/2026-07-09-incidente-pacs008-canonico-pixin-handoff.md:9`).

### 2.6 Core: consumo do evento e credito TB + PG

`core/backend/lib/monetarie/infra/nats/consumers/pix_consumer.ex`:

- Consumidor durable `core-pix-consumer` do stream `MONETARIE_SPI`, subjects `monetarie.spi.transaction.*` etc. (29-37), `max_deliver = 4` (237-238). Dedup por `processed_messages` com msg_id deterministico (86-104). Retry local 3x com backoff exponencial; esgotou = DLQ NATS + tabela `nats_dead_letters` + ACK (268-307).
- Conversao de unidade na fronteira: `MoneyBoundary.convert_inbound` (BRL/centavos da cabine -> subcentavo do Core) antes do dispatch (165-173).
- Roteamento: `AtomicPaymentHandler.maybe_handle` primeiro (so casa quando existe `outbound_request`, ou seja PIX-out; para PIX-in retorna `:not_found`; `core/backend/lib/monetarie/use_cases/payments/atomic_payment_handler.ex:24-36`) e cai no `PixHandler.handle_transaction(payload, type)` (175-185).
- Caminho de credito `handle_transaction_ledger` (`core/backend/lib/monetarie/infra/nats/handlers/pix_handler.ex:349-434`): journal COSIF (832-869) + transfer TB via `Wallet.deposit` com transfer_id deterministico SHA-256 do reference ("Same reference always produces same ID, idempotent on retry"; 783-798, 1112-1129) + registro em `transactions` + lancamento no extrato `account_entries` (`StatementEntries.record_inbound_settled`, idempotente por reference+source, fail-soft; 410-415) + tarifa pos-processamento nao bloqueante (417-425, 891+). `Wallet.deposit` credita cash asset -> client liability com `TransferCode.deposit()` (`core/backend/lib/monetarie/use_cases/wallet.ex:159-183`).

### 2.7 ACHADO CRITICO: divergencia codigo vs modelo canonico no credito do Core

Este e o achado mais importante da trilha e precisa de decisao antes do primeiro PIX-in organico de producao.

- O que os DOCUMENTOS afirmam: "O caminho de credito correto e o LEGADO (flag OFF...): pacs.008 chega, cabine valida, credita PI, publica transaction.created (outbox), responde ACSP, avanca ACCC; Core handle_transaction_ledger credita TB + PG + extrato + tarifa" (`docs/handoff/2026-07-09-incidente-pacs008-canonico-pixin-handoff.md:23`; mesmo texto no CLAUDE.md raiz). O runbook do primeiro PIX espera no Core o audit `PIX_TRANSACTION` e o credito TB+PG por E2E (`docs/operator/2026-07-09-runbook-primeiro-pix-real-prod.md:44-47`).
- O que o CODIGO em `origin/main` (76b1dc17) faz: `PixHandler.handle_transaction(payload, "created")` e AUDIT-ONLY e retorna `:ok` sem tocar ledger, "even when the legacy two-phase feature flag is unset" (`core/backend/lib/monetarie/infra/nats/handlers/pix_handler.ex:48-65`). A clausula existe desde o commit `7c6957c5` de 2026-06-25 ("Handle PIX transaction.created callbacks safely", `git log -S 'transaction.created audit-only'`). O catch-all que chamaria `handle_transaction_ledger` (156-180) NAO e alcancado por "created" porque a clausula especifica vem antes. O evento "settled" para untracked credita (92-119), mas para PIX-in nenhum "settled" e publicado: a cabine so publica `transaction.created` no aceite (inbound_processor:931) e `status_update`/`transaction.settled` dependem de pacs.002 recebida, que no PIX-in nao existe (canonico); o handler de camt.054 tambem nao publica status (`inbound_processor.ex:1219-1293`).
- Consequencia (leitura estatica; NAO VERIFICADO em runtime): um PIX-in organico hoje produz no Core apenas o audit `PIX_TRANSACTION_CREATED`; o credito TB + `account_entries` + tarifa NAO acontece automaticamente. No incidente de 2026-07-09 o credito do cliente foi aplicado por remediacao manual (`Wallet.deposit` + account_entry, handoff linha 6), e os "6 audits PIX_TRANSACTION_REJECTED_UNTRACKED" provam que os EVENTOS chegam ao Core, nao que o credito flui. O lado SPI acredita que "o credito ao cooperado vai por este evento" (inbound_processor:928-930); o lado Core o trata como pre-settlement e espera um settled explicito que ninguem emite para PIX-in. Contrato quebrado entre os dois lados.
- O proprio runbook do primeiro PIX detectaria isso (item 5 esperaria `PIX_TRANSACTION` e nao apareceria), mas detectar no primeiro PIX real de um cliente significa cliente sem credito ate remediacao manual.

---

## 3. Diferencas estruturais e por que existem

| Dimensao | AVIV (indireto, OnZ) | Monetarie (direto, BACEN) |
|---|---|---|
| Quando o dinheiro e certo | So no ACCC (OnZ entrega pacs.008 pre-liquidacao) | Antes da pacs.008 (BACEN ja liquidou) |
| Fases | 2 (ACSP guarda intent; ACCC deposita) | 1 (credita e responde ACSP no mesmo processamento) |
| Se a confirmacao nunca chega | Redis TTL 600s + WAL `acsp_sent` + MGMT fallback + orphan recon 15min | Nao ha "confirmacao a esperar"; o risco e outro (evento ao Core nao consumido) |
| Transporte | HTTP porta 80 sem TLS, sem assinatura nossa (OnZ assina) | mTLS ICP-Brasil + XMLDSig HSM nas respostas, verify fail-closed no inbound |
| ACK ao provedor | Cursor LP (avanca so apos persist duravel) | Proximo GET ICOM (so apos `AckTracker.persist_batch`) |
| Broker interno | Nenhum (NATS descomissionado); Oban como fila duravel | NATS JetStream durable + outbox Oban atomico |
| Ledger | TB direto no app da cabine (mesmo processo), batch 3-4+ pernas COSIF | TB no Core, outro servico, 1 transfer deposit; COSIF via journal PG |
| Espelho PG | MERGE stored procedure, PK composta ancorada no timestamp TB | `transactions` + `account_entries` idempotentes por referencia |
| Webhook cliente | `pix.charge.paid` inline pos-MERGE com dedup contra o job Oban | Nao identificado no caminho PIX-in do Core (NAO VERIFICADO se o modulo de webhooks Onda 3 cobre PIX-in) |

Numero de escritas sincronas no caminho critico ate o "aceite" (contagem estatica):

- AVIV ate o ACSP: INSERT inbox (PG) + enqueue Oban (PG) + `mark_processing` (PG) + INSERT `processed_messages` (PG) + SET Redis pending + HTTP ACSP OnZ + INSERT WAL `acsp_sent` (PG). ~5 escritas PG + 1 Redis + 1 HTTP. Na Fase 2 ate o dinheiro: dedup (PG) + WAL (PG) + enqueue Oban (PG) + 1 round-trip TB (batch linked) + MERGE (PG) + fee (PG).
- Monetarie ate a pacs.002 ACSP enfileirada: persist ICOM (PG) + publish NATS + SET Redis dedup + verify XMLDSig + validate XSD (2 execucoes xmllint) + RPC Core 200ms + `Repo.transaction` (2 INSERTs PG + 1 enqueue Oban) + update PI balance (PG) + enqueue pacs.002 (PG) + UPDATE ACCC (PG). Depois, fora do caminho do worker: assinatura HSM (rede) + XSD + POST mTLS no OutboundSender; e no Core dedup (PG) + audit (PG) + journal + TB + transactions + account_entries + fee.

Observacao de latencia: o caminho critico Monetarie carrega no worker inbound um RPC sincrono ao Core (ate 200ms), duas validacoes xmllint e, na resposta, uma assinatura HSM via rede. Sao os pontos quentes para a meta de volume alto; a AVIV nao tem nenhum equivalente (sem assinatura, sem RPC de validacao sincrono, validacoes em Elixir puro).

Garantias de nao-perda e nao-duplicacao:

- AVIV: nao-perda por cursor-ACK condicionado a persist + reentrega OnZ ~5x + 3 sweeps; nao-duplicacao por `processed_messages` + claim de estado do inbox + IDs TB deterministicos + MERGE + dedup de webhook. Exatamente-uma-vez de EFEITO mesmo com pool local e Oban rodando a mesma mensagem (`onz_lp_inbox_processor.ex:163-173`).
- Monetarie: nao-perda por ACK-ICOM condicionado a persist + JetStream durable + outbox Oban atomico + NAK/DLQ/poison; nao-duplicacao por Redis dedup + advisory lock em transacao + `processed_messages` no Core + transfer_id TB deterministico SHA-256. A EXCECAO e o elo Core do credito (secao 2.7): a garantia existe na entrega do evento, mas o efeito (credito) esta desligado no codigo atual.

---

## 4. O que a AVIV tem de melhor (candidatos a port) com esforco estimado

1. Reconciliacao de orfaos dirigida pela verdade externa (`PixInOrphanReconciliation`, cron 15min, lookback 30 dias, auto-deposito limitado, advisory lock global; `pix_in_orphan_reconciliation.ex:1-47`). A Monetarie tem TB==PG checkpoint e reconciler MED, mas nao tem um sweep que compare a verdade BACEN (camt.054 CRDT / camt.060) com os creditos aplicados no Core por E2E. Foi exatamente essa classe de verificacao (camt.060 -> camt.054) que resolveu o incidente de 07-09 manualmente. Esforco: medio (worker Oban no Core ou na cabine + consulta ao acervo camt.054 ja persistido em `bacen_inbound`).
2. WAL de deposito com maquina de estados + recovery dedicado (`pending_pix_in_deposits` acsp_sent/pending/tb_done/pg_done + `PixInRecoveryWorker` 5min). No Core Monetarie, se `handle_transaction_ledger` falhar apos o TB e antes do PG (ou vice-versa), o rastro fica so em log/DLQ; nao ha row de estado re-executavel por maquina. Esforco: medio.
3. Monitor de taxa de sucesso do PIX-in (`PixInOkRateMonitor` + `LagWindow`/`CursorWindow`, cron 5min): alerta quando a proporcao MSG_OK despenca ou o fluxo estanca. A Monetarie tem watchdogs de fila (port de 07-04) mas nao um monitor de OK-rate especifico do money path de entrada. Esforco: baixo (os logs PERF por fase ja foram portados; falta o agregador + alerta).
4. `DepositCoalescer` do TB (flag OFF; `tigerbeetle.ex:600-618`): amortiza round-trips do TB agregando batches de depositos concorrentes. Relevante para a meta de volume >1,5M/dia. Esforco: medio (GenServer + flag opt-in, padrao que o Core ja usa nos caches de 07-04).
5. Persistencia de TODA rejeicao de entrada em `failed_transactions` com reason_code para metrica de taxa de sucesso (`handler.ex:1038-1072`). A Monetarie registra a rejeicao em `bacen_inbound`/messages e publica `transaction.rejected`, mas nao ha tabela dedicada de funil com categoria. Esforco: baixo.
6. Webhook ao cliente no credito (`pix.charge.paid` inline pos-MERGE, dedup contra o safety-net; `pg_writer.ex:155-274`, `pix_in_pg_write_job.ex:144-148`). Se o produto Monetarie (Partner API) precisar notificar o parceiro no PIX-in, este desenho (inline apos persistencia confirmada + job de recuperacao sem side effects) e o correto. Esforco: medio. NAO VERIFICADO se o modulo de webhooks existente do Core ja cobre esse evento.
7. Timestamp do ledger como ancora temporal (DRIFT-01, `pg_writer.ex:71-88`): usar o commit timestamp do TB como `started_at` do espelho PG garante idempotencia byte-identica em retry com PK composta particionada. O Core Monetarie usa particionamento por `started_at`; vale auditar se os retries do PixHandler produzem sempre o mesmo `started_at`. Esforco: baixo-medio (auditoria + ajuste pontual).

O que NAO portar: o two-phase em si (TWO_PHASE_PIX_IN e PROIBIDO na Monetarie; o gatilho settled so existe no simulador, ligar = cliente nunca creditado; handoff 07-09 linha 24) e toda a mecanica de slots/cursor/MGMT-fallback, que so existe porque a OnZ esta no meio.

---

## 5. O que a Monetarie tem de melhor (nao regredir)

1. Liquidacao garantida ANTES do processamento: sem fase 2, sem intent no Redis, sem janela ACSP->ACCC, sem classe inteira de orfaos "liquidado no BACEN mas nao confirmado no provedor". Metade das redes de seguranca da AVIV existe para compensar a OnZ.
2. Latencia de transporte: 24-28ms por GET ICOM vs p50 471ms do long-poll OnZ (comparativo 07-04; a propria AVIV documenta P95/P99 acima da meta BACEN de 500ms em `poller.ex:106-108`).
3. Seguranca de mensageria: XMLDSig verify fail-CLOSED no inbound, assinatura HSM fail-CLOSED + gate XSD no outbound (`outbound_sender.ex:85-102`), RejectCodes como fonte unica com guarda de build (`reject_codes.ex`). A AVIV nao valida assinatura nem XSD do que recebe da OnZ (confianca no provedor, HTTP porta 80).
4. Outbox atomico com guarda de lint/AST (`Shared.Nats.Publisher.publish_async` dentro de `Repo.transaction`, ADR-008/009/010) em TODOS os emits de money path do worker (`inbound_processor.ex:66-74`): elimina a classe "publicou sem gravar" e "gravou sem publicar".
5. Decisao pura e testavel do desfecho do pacs.008 (`inbound_outcome/4`) com short-circuit e retry NAK para indisponibilidade transitoria: nunca rejeita credito real por falha nossa (licao do incidente, ja codificada).
6. Devolucao automatica pacs.004 quando a conta destino nao pode receber (AC03/AC06/AC07/AC14/AG03): o dinheiro liquidado que nao pode ser creditado volta ao pagador sem intervencao (481-493).
7. ACK ao provedor condicionado a persistencia nos DOIS lados (AckTracker no ICOM, cursor pos-persist na AVIV): aqui os dois estao bem, mas a Monetarie ainda soma o JetStream durable entre a ponte e o worker.

---

## 6. Recomendacoes priorizadas (meta: mais volume, menos latencia)

P0 (antes do primeiro PIX-in organico de producao):

1. Resolver a divergencia do credito no Core (secao 2.7). Ou o `PixHandler` volta a creditar em `transaction.created` para untracked inbound (alinhando ao canonico documentado), ou a cabine passa a emitir o evento de liquidacao que o Core espera. Evidencias: `pix_handler.ex:48-65` (audit-only) vs handoff 07-09 linha 23 e runbook itens 5-7. Enquanto nao resolvido, o runbook do primeiro PIX deve tratar "audit PIX_TRANSACTION_CREATED sem credito TB" como desfecho ESPERADO do codigo atual e ter o passo de remediacao pronto.
2. Teste de contrato cross-repo cabine->Core do PIX-in (evento publicado vs clausula consumida), para essa classe de quebra nunca mais passar silenciosa. O lado SPI documenta o contrato em comentario (`inbound_processor.ex:928-930`), mas nada o executa contra o Core.

P1 (volume + resiliencia):

3. Portar a reconciliacao de orfaos com verdade BACEN: sweep periodico comparando camt.054 CRDT persistidas (`bacen_inbound`) com creditos aplicados no Core por E2E, com auto-remediacao limitada e advisory lock (modelo `pix_in_orphan_reconciliation.ex`). E a rede que teria pego o incidente de 07-09 em minutos em vez de investigacao manual.
4. Tirar do caminho critico do inbound o que nao precisa estar nele: (a) validacao de conta no Core via RPC sincrono de 200ms pode ter cache curto por conta/chave (a AVIV resolve conta com 1 Repo.get + caches); (b) as duas execucoes xmllint (verify + XSD) merecem medicao e, se dominantes, pool/porta nativa; (c) `update_pi_balance` pode ser assincrono-durable (hoje e sincrono no worker e fail-soft em rescue, `inbound_processor.ex:2914-2945`).
5. Monitor de OK-rate do PIX-in (agregando os logs PERF ja existentes) + alerta de profundidade de DLQ/poison, espelhando `PixInOkRateMonitor`/`OnzDlqDepthMonitor` (`runtime.exs:714-719`).
6. WAL de estado do credito no Core (equivalente enxuto de `pending_pix_in_deposits`): row por E2E com estados recebido/tb_done/pg_done e recovery idempotente, cobrindo crash entre TB e PG (hoje o transfer_id deterministico protege contra duplicacao, mas nao ha re-execucao automatica dirigida por estado).

P2 (otimizacao):

7. `DepositCoalescer` opt-in para o TB do Core sob pico (>X depositos/s), com flag OFF default (modelo `tigerbeetle.ex:600-618`).
8. Tarifa do PIX-in no mesmo batch TB do credito (modelo T4 clampado da AVIV, `deposit.ex:91-104,255-287`) em vez de pos-processamento separado, reduzindo round-trips e garantindo atomicidade tarifa/credito.
9. Webhook de credito ao parceiro (se o produto exigir) no desenho inline-apos-persistencia com job de recuperacao sem side effects (`pg_writer.ex` + `pix_in_pg_write_job.ex:144-148`).
10. Auditar a estabilidade do `started_at` do espelho PG do Core em retries (licao DRIFT-01 da AVIV), dado o particionamento por `started_at`.

---

## 7. Perguntas abertas / NAO VERIFICADO

1. NAO VERIFICADO (runtime): qual valor de `ONZ_LP_INBOX_CONSUMER` esta aplicado na AVIV PRD/HML. O default do codigo e `:sync` (`runtime.exs:1054-1060`); os comentarios afirmam `:processor` como default operacional. Sem AWS nesta trilha, fica em aberto.
2. NAO VERIFICADO (runtime): se `TB_DEPOSIT_COALESCE_ENABLED` e/ou `ONZ_LP_LOCAL_PROCESSOR` estao ligadas em PRD da AVIV (defaults false no codigo).
3. NAO VERIFICADO: se o modulo de webhooks da Monetarie (Onda 3, "webhooks honestos") dispara algum evento ao parceiro no PIX-in liquidado; esta trilha nao encontrou dispatch no caminho `handle_transaction_ledger`.
4. ABERTO: qual e a intencao do dono para o elo Core do credito (secao 2.7): restaurar credito em `created` (com as guardas F-07 de status) ou introduzir um evento de liquidacao explicito da cabine. As duas opcoes tem implicacoes no runbook do primeiro PIX e no import/replay de eventos antigos (regra: NUNCA reprocessar a pacs.008 do incidente na DLQ, duplo credito).
5. ABERTO: medicao real do custo por fase do inbound Monetarie (xmllint verify, xmllint XSD, RPC Core, HSM sign) sob carga; os logs PERF por fase (Task B1, `inbound_processor.ex:160-170`) ja existem para essa medicao.
6. Observacao de codigo morto/desativado conferida: `TbFirst.Handler.handle_settled` do Core so roda com `two_phase_pix_in_enabled=true` (`pix_handler.ex:112-113`), flag PROIBIDA e conferida OFF nos 4 task-defs (handoff 07-09 linha 24); nao faz parte do caminho vivo.
