# Trilha 03: PIX-out money path completo, AVIV via OnZ vs Monetarie direto no SPI

Data: 2026-07-09. Trilha READ-ONLY do mandato de mapeamento exaustivo AVIV/coreproviders vs cabine Monetarie.
Toda afirmacao tecnica abaixo cita arquivo:linha. Onde nao foi possivel provar, esta marcado NAO VERIFICADO.
Convencao de caminhos: `cp/` = `/Users/luizpenha/coreproviders`, `mon/` = `/Users/luizpenha/monetarie`.

---

## 1. Como a AVIV faz (participante INDIRETO, provedor OnZ CloudPIX)

### 1.1 Visao geral do desenho

A AVIV nao fala com o BACEN. Ela monta um payload JSON e faz POST HTTP puro no provedor OnZ (`/v3/icom/pacs/008`); a OnZ e quem assina, valida XSD e conversa com o SPI. A confirmacao (pacs.002) volta por long-poll HTTP na OnZ, nao por push. O dinheiro do cliente e reservado em TigerBeetle (transfer pending two-phase) ANTES do envio, e so vira debito definitivo (post) quando o pacs.002 terminal chega.

Fluxo resumido:

```
controller/API externa/IB
  -> Orchestrator.execute (validacao, aprovacao, limites)
  -> run_pipeline: drain -> BalanceGuard(min(TB,PG)) -> advisory lock -> TimeBlock -> BalanceCheck+LimitCheck -> insert outbound_request(stage 0) -> TB pending (stage 1)
  -> send_request: DICT lookup encadeado (cache/balde) -> POST pacs.008 na OnZ (stage 2)
  -> resposta sincrona "accepted" ao cliente
  -> LP Poller (long-poll OnZ) entrega pacs.002 -> AtomicPaymentHandler -> Pix.on_reply
       ACSP/settled: TB post (efetiva debito) + move_to_transactions + webhook confirmed
       RJCT: TB void (estorna reserva) + move_to_failed + webhook failed
  -> FastTrack (T+2s/4s/9s) consulta GET /v3/icom/mgmt/{e2e} como rede de seguranca
```

### 1.2 Iniciacao e validacao

- Entry point unico: `Fluxiq.UseCases.Payments.OutboundPayment.Orchestrator.execute/2` (`cp/backend/lib/fluxiq/use_cases/payments/outbound_payment/orchestrator.ex:39-101`). Serve controllers, API externa, executor de agendados.
- Handler PIX: `cp/backend/lib/fluxiq/use_cases/payments/outbound_payment/pix.ex`. `validate_and_normalize/3` (pix.ex:47-91) roda, em ordem: valida amount, kill switch global `PIX_OUT_DISABLED` (pix.ex:749-764), client_request_id, resolucao de conta/entidade, sanitizacao da chave, bloqueio de auto-transferencia (pix.ex:766-780), bloqueio intra-institucional (mesmo ISPB = TEF, pix.ex:782-794), resolucao do E2E, calculo de fee.
- Alcada: `maybe_require_approval` (orchestrator.ex:883-920) cria `pending approval` via `ApprovalEngine.needs_approval?` por valor antes do pipeline.
- Kill switch de teto por transacao: `check_kill_switch_ceiling` roda DENTRO do advisory lock, default 100_000_000 BU = R$ 10.000, override `PIX_OUT_MAX_AMOUNT_BU` (orchestrator.ex:337-375).
- Gate CNPJ-out por conta (`accounts.block_pix_out_cnpj`) com whitelist e exigencia de documento do recebedor (orchestrator.ex:403-577).

### 1.3 E2E: reserva, geracao provisoria, e o canonico da OnZ

- `resolve_e2e_id/3` (pix.ex:892-933): prioridade e (1) `reservation_id` de uma `DictE2eReservation` persistida (marca `used_at`, rejeita expirada/reusada, pix.ex:892-916); (2) E2E explicito do chamador; (3) geracao local `Fluxiq.ID.generate_e2e(ispb)`.
- CRITICO: o E2E local e apenas PROVISORIO. Na consulta DICT o `idFimAFim` NAO e enviado: "It is deliberately NOT sent as idFimAFim; OnZ must generate and return the canonical EndToEndId, and that value must be used in the subsequent PACS.008" (`cp/backend/lib/fluxiq/services/pix_providers/onz/endpoints/dict.ex:28-34`; a query so leva `tipoChave`, dict.ex:44-63).
- No cache MISS o pacs.008 sai com o E2E devolvido pela OnZ (`pacs_e2e = dict_provider_e2e`, adapter.ex:637-642); no cache HIT a iniciacao vira MANU e usa o E2E provisorio (adapter.ex:634-641), porque um pacs.008 DICT sem consulta correspondente seria uma "liquidacao DICT orfa" que o BACEN penaliza (comentario adapter.ex:554-560, ticket BACEN de 2026-06-19).
- Enriquecimento pos-envio: `enrich_request_from_response/2` grava no `outbound_request` o E2E do provedor (substitui o provisorio via `request_attrs = %{end_to_end_id: provider_e2e}`), nome/documento/ISPB/conta do recebedor e estatisticas antifraude do DICT (pix.ex:694-741).

### 1.4 Debito/reserva TB antes do envio (pipeline atomico)

- `run_pipeline/3` (orchestrator.ex:121-250): (1) `maybe_drain` fora do lock; (2) `BalanceGuard.verify` pre-lock, onde `safe_balance = min(tb_available, pg_available)` (defesa contra credito fantasma, `cp/backend/lib/fluxiq/use_cases/payments/pix_out/balance_guard.ex:14-21,55,116,248-262`); (3) `locked_admission` abre `Repo.transaction` + `pg_advisory_xact_lock(band(account_id, 0x7FFF...))` (orchestrator.ex:298-302) e roda TimeBlock -> `BalanceCheck.verify_within_lock` (TB + MED nao fundeado + `LimitCheck.verify`) -> teto kill switch -> gate CNPJ -> insert `outbound_request` stage 0 -> `create_pending`.
- `create_pending` cria em TB a reserva two-phase: transfer `pending: true` cliente -> transito (`pix_out_transit`) + cliente -> receita de tarifa, batch linked (`cp/backend/lib/fluxiq/use_cases/payments/pending_transfers.ex:65-105`). A trava de saldo e o proprio TigerBeetle: `debits_must_not_exceed_credits` (orchestrator.ex:6-10); `exceeds_credits` => apaga a linha PG e devolve `insufficient_balance` (orchestrator.ex:622-646).
- Erro AMBIGUO de infra no create_transfers (timeout): NAO deleta a linha; deixa stage 0 para o sweeper e responde 202 accepted com marcador `pending_recovery` (orchestrator.ex:648-714). Incidente real citado no codigo: conta 10203, R$ 4.850,27, 2026-07-01.
- Otimizacao de lock em flags (todas default OFF): `pixout_tb_out_of_lock` (Fase B: TB fora do lock; o comentario de config documenta que o round-trip TB era "~68ms/p99-1235ms, 48%/85% do hold do lock", `cp/backend/config/runtime.exs:213-224`) e `pixout_lookup_prelock` (snapshot TB pre-lock, runtime.exs:226-236). `pixout_in_transit_pg` alimenta o limite com SUM PG em vez de TB (runtime.exs:202-212).
- IDs semanticos: `compute_semantic_root` deriva o root do batch TB do proprio E2E (`BatchChain.root_id`), o que torna replays idempotentes e permite lookup reverso E2E -> transfers TB (pix.ex:96-98,1173-1180; pending_transfers.ex:13-19).

### 1.5 Envio a OnZ (DICT lookup encadeado + pacs.008)

- `send_request/1` resolve o `ONZ-PayerId` (CNPJ do titular da conta, `PayerResolver.resolve_from_account`, pix.ex:132-138) e chama `Provider.send_pix`.
- Adapter OnZ (`cp/backend/lib/fluxiq/services/pix_providers/onz/adapter.ex:37-66`): pagamento com chave e sem dados do recebedor => `send_pix_via_dict`. Encadeamento com circuit breaker (`HealthCheck.healthy?`, adapter.ex:458-464), cache Redis da consulta DICT (TTL default 180s, configuravel via `pix_settings.dict_cache_ttl_seconds`, adapter.ex:741-759), rate limit por merchant (`ClientLimiter`, default 120/min, adapter.ex:475-491 e `dict_bucket/config.ex:68-70`) e reserva de ficha no espelho local do balde BACEN (`DictBucketGateway.authorize_query` com prioridade `:essential` para PIX-out, adapter.ex:344-375).
- Balde DICT (anti-scan BACEN, Manual DICT v8.1 par. 13.1): tabela de ratings A-H em `cp/backend/lib/fluxiq/services/pix_providers/onz/dict_bucket/config.ex:2-11` (G = capacidade 250, refill 25/min; producao AVIV roda rating G conforme `cp/docs/coreproviders/dict-bucket-strategy.md` secao 2, revalidado ao vivo em 2026-06-04). Custos: 200 = 1 ficha, 404 = 3 fichas, 429 = prova de balde vazio e zera o espelho (config.ex:50-58; adapter.ex:793-838). A ficha da consulta so retorna no terminal SPI (ACSC/RJCT) via `DictTokenCredit.credit_on_terminal` chamado pelo `AtomicPaymentHandler` (atomic_payment_handler.ex:97-105).
- Timeout da consulta DICT sincrona: `receive_timeout` 2500ms default (`DICT_LOOKUP_TIMEOUT_MS`), retry interno OFF, 1 retry externo de 300ms, pior caso ~5.3s (dict.ex:171-196).
- pacs.008: `Icom.send_pix` faz `POST /v3/icom/pacs/008` com body JSON montado por `Mapper.to_pacs008` (`cp/backend/lib/fluxiq/services/pix_providers/onz/endpoints/icom.ex:17-34`). Nao ha assinatura local, nao ha XSD local, nao ha mTLS local: o transporte e o `Onz.Client` com `base_url` por env (`ONZ_BASE_URL`, `cp/backend/config/runtime.exs:956`); o contexto ja verificado do mandato registra HTTP puro porta 80 via VPC peering.
- `account_type` do DICT (CACC/TRAN/SVGS) e propagado para evitar AC14 (adapter.ex:627-630); mesmo ISPB no DICT => rejeita e manda usar TEF (adapter.ex:679-689).

### 1.6 Desfechos sincronos do envio (`finish/1`)

- Sucesso do provedor: stage 2, webhook `pix.payout.processing`, agenda FastTrack e responde `accepted` (pix.ex:172-197).
- Erro PERMANENTE 4xx (400/403/404/409/412/422): classifica o codigo real da OnZ (`ReasonCodes.classify_provider_error`), voida os pendings TB e move para failed, respondendo o mesmo codigo no sincrono e no webhook (pix.ex:199-232,291-331).
- Excecao importante: `dict_lookup_failed` por timeout/erro de infra NAO e permanente; entra na fila de retry com reason proprio `dict_unavailable` (snooze inicial 10s), porque nenhuma pacs.008 foi enviada (pix.ex:214-231,336-347). Bug real citado: clientes cancelavam localmente pagamentos vivos (producao 2026-07-01).
- Rate limited/balde esgotado: se `pix_out_retry_queue_enabled` (env `PIX_OUT_RETRY_QUEUE_ENABLED`, default false em codigo, TRUE em HML e PRD conforme dict-bucket-strategy.md secao 2), enfileira stage 4 no Oban `PixOutRetryWorker` com snooze 3s (alinhado ao refill do rating G: 25/min = 2.4s por ficha) e TTL 120min; o `queued_at` original e PRESERVADO no re-enqueue para o TTL disparar (bug real de 2026-07-02: pagamento de chave invalida em loop por 5h+, ~80 lookups DICT/min) (pix.ex:234-266,349-451; `cp/backend/lib/fluxiq/workers/pix_out_retry_worker.ex:42-96`). No TTL: void TB + move_to_failed + webhook `pix.payout.failed` reason `queue_timeout` (pix_out_retry_worker.ex:100-159).
- Erro TRANSIENTE (5xx/timeout/auth): fica em stage 1, responde `accepted`, e o `AtomicPaymentStaleChecker` (cron Oban `*/2min`, `cp/backend/config/runtime.exs:633`) recupera: re-envia, consulta status ou forca void apos janelas configuraveis (`cp/backend/lib/fluxiq/use_cases/payments/atomic_payment_stale_checker.ex:30-46`, com quarentena para casos api_sent antigos, :312-386).

### 1.7 Confirmacao: long-poll, FastTrack e aplicacao do pacs.002

- Caminho primario: `Onz.Poller`, GenServer de long-poll com slots (max pollers via Redis SETNX, lease TTL 15s, heartbeat 5s, ciclo 5000ms com stagger 833ms por slot, `cp/backend/lib/fluxiq/services/pix_providers/onz/poller.ex:1-121`), N instancias sob `PixProviders.Supervisor` (`cp/backend/lib/fluxiq/services/providers/../pix_providers/supervisor.ex:58-68`). `receive_timeout` 10s com historico de regressoes documentado (poller.ex:41-60). Latencia medida em producao (comentario com evidencia 2026-04-20 PRD, 1256 tx/60min): PIX-IN fim a fim P50 471ms, P95 561ms, P99 737ms (poller.ex:106-108). Para PIX-OUT, o proprio codigo declara "Long-Poll e o caminho primario (median 1.6-2.0s end-to-end)" (pix.ex:1300-1310).
- Roteamento do inbound: `dispatch_inbound` (poller.ex:1657-1726) manda status de outbound (`settled/accepted/rejected/...`) para `AtomicPaymentHandler.maybe_handle` (poller.ex:1691-1695), que resolve o `outbound_request` por transaction_id/E2E/OrgnlInstrId (refund-out) e despacha `handler.on_reply` (`cp/backend/lib/fluxiq/use_cases/payments/atomic_payment_handler.ex:19-95`).
- `Pix.on_reply` settled: monta o POST two-phase (post_pending_transfer dos pendings + 2 transfers diretos transito->SPI e SPI->client_liability + splits de tarifa + espelho caixa), tudo linked atomico em TB, `move_to_transactions`, telemetria com `settlement_ms`, webhook `pix.payout.confirmed` (pix.ex:552-640; pending_transfers.ex:116-170).
- `Pix.on_reply` rejected: `PendingTransfers.void_with_retry` (3 tentativas, backoff 100ms exponencial) e `move_to_failed` com reason_code BACEN (ex.: AC03) ou codigo do provedor (pix.ex:642-682; pending_transfers.ex:223-294).
- VoidGuard: antes de QUALQUER void de PIX, consulta a OnZ MGMT; se o BACEN ja liquidou (CONCLUIDA/ACSC/ACCC/...), RECUSA o void ("guard contra perda de R$ 105k King-style, incidente 2026-04-20", pending_transfers.ex:236-268).
- FastTrack (rede de seguranca do LP): task assincrona consulta `GET /v3/icom/mgmt/{e2e}` em T+2s, T+4s, T+9s; se o Poller ja resolveu, para sem chamar a OnZ (pix.ex:1300-1366; `query_status` parseia dezenas de variantes de status, pix.ex:495-526,1127-1171).
- Poison policy: falha permanente de processamento no LP nao trava o cursor; apos 5 falhas consecutivas (`ONZ_POISON_MAX_RETRIES`) a mensagem vai para DLQ + linha `poisoned` em `onz_lp_inbox` + alerta, e o cursor avanca (poller.ex:31-39).

### 1.8 PIX agendado, limites e observabilidade

- Agendado: tabela `pix_scheduled` + `Fluxiq.Workers.PixScheduledExecutionWorker` em cron Oban a cada minuto (ate 100 por tick), reusando `Orchestrator.execute` (`cp/backend/lib/fluxiq/workers/pix_scheduled_execution_worker.ex:1-78`; cron em `cp/backend/config/runtime.exs:705`).
- Limites: `Limits.LimitCheck` unificado (transacao/diario/mensal). ATENCAO: o enforcement NOTURNO foi REMOVIDO na AVIV; `maybe_check_nighttime` existe mas o corpo sempre retorna `:ok` ("Per session 163 (gotcha #625), nighttime enforcement was removed project-wide", `cp/backend/lib/fluxiq/use_cases/limits/limit_check.ex:22-26,69`).
- Observabilidade do hot path: logs de timing por fase no orchestrator (`validate=..ms pipeline=..ms send+settle=..ms total=..ms`, orchestrator.ex:60-68), telemetry pix_out_completed/rejected, metricas da fila de retry, e o instrumento PERF `DICT_DONE->ICOM_START` (adapter.ex:694-703).
- Artefatos de evidencia operacional no repo: `cp/dict-bucket-pixout-23min-2026-06-15.xlsx`, `cp/pixout-onz-status-2026-06-01.xlsx`, `cp/voided-12-pendente-onz-URGENTE.csv` (existencia verificada por listagem; conteudo dos xlsx NAO VERIFICADO nesta trilha, sao binarios). A mecanica que explica o "23min" do nome do arquivo esta no codigo/doc: rating G tem 250 fichas e refill 25/min, e a fila stage 4 retenta por ate 120min (dict-bucket-strategy.md secoes 1-3; pix.ex:336-343).

---

## 2. Como a Monetarie faz (participante DIRETO no SPI, cabine propria)

### 2.1 Visao geral do desenho

A Monetarie tem dois aneis: o Core (conta do cliente, TigerBeetle + espelho PG) e a cabine PIX (umbrella `spi_service`/`shared`, dona do dialogo com o BACEN: pacs.008 XML assinado via HSM, mTLS ICP-Brasil, ICOM long-poll direto no BACEN). A comunicacao Core -> cabine e por NATS JetStream com outbox atomico.

```
IB/Partner API -> Core OutboundOrchestrator
  (advisory lock: BalanceCheck.lock_and_verify -> TimeBlock -> LimitCheck com NOTURNO -> insert outbound_request -> TB pending)
  -> handler Outbound.Pix.send_request -> Provider.send_pix -> InHouse adapter -> NATS monetarie.core.pix.payment_request
-> cabine CoreEventProcessor (settlement_service)
  (E2E OBRIGATORIO da consulta DICT cacheada; fail-closed se expirou)
  (debit-then-send: bloqueio no saldo espelho da Conta PI, ISPB 46026562)
  (valida semantica pacs.008 -> monta XML -> outbox: transaction.created + outbound.send)
-> OutboundSender (spi_service)
  (validacao estrutural -> sancoes -> assinatura XMLDSig no HSM RTM fail-closed -> gate XSD pos-assinatura fail-closed -> POST ICOM)
  (ACK HTTP = status 2 ACSP local + confirm_block; ACK NAO liquida)
-> BACEN processa; pacs.002 chega pelo ICOM CPM Worker (long-poll nosso, pre-ACK durability)
-> InboundProcessor.process_status_report (correlacao por OrgnlEndToEndId; fail-CLOSED: NAK se nao aplicar)
-> StatusUpdater (guards terminais; ACSC/ACCC/STLD -> settled: confirma bloqueio, checa ANS 1.6s, outbox transaction.settled)
-> Core PixHandler (tracked outbound: TB ja debitado na iniciacao; registra extrato; rejected -> libera hold)
```

### 2.2 Iniciacao no Core (limites, noturno, TB pending)

- Entry points: `mon/core/backend/lib/monetarie_web/controllers/v2/transfer_controller.ex:305` e Partner API (`partner_v1/transfers_controller.ex:34,556`) chamam `Monetarie.UseCases.Payments.OutboundOrchestrator.execute/1`.
- Pipeline (`mon/core/backend/lib/monetarie/use_cases/payments/outbound_orchestrator.ex:60-117`): `BalanceCheck.optimistic_check` -> `locked_insert` com `BalanceCheck.lock_and_verify` (advisory lock, comentario :77-80) -> `verify_time_block` (TimeBlocks por entidade) -> `verify_limits` -> insert `outbound_request` -> `create_pending` em TB (transfers pending; `:exists` idempotente promove a stage 1, :161-198).
- Limites com NOTURNO ENFORCADO: `Monetarie.UseCases.Limits.LimitCheck` e port do modulo da AVIV COM a diferenca deliberada: "coreproviders shipped maybe_check_nighttime collapsed to :ok (enforcement removed)... inside the nighttime window (default 20:00-06:00 America/Sao_Paulo) a PIX-OUT/TED above the resolved nighttime_limit is rejected" (`mon/core/backend/lib/monetarie/use_cases/limits/limit_check.ex:2-27,42-43,76-88`). Res. BCB 142 (R$ 1.000 noturno configuravel por cliente) implantada na Onda 1 (handoff `mon/docs/handoff/2026-07-02-paridade-ecossistema-ondas-1-3-handoff.md`, secao Onda 1; CLAUDE.md raiz, estado 2026-07-02).
- `dispatch_send` embrulha `send_request` + `finish` em `Repo.transaction` (publish outbox + update stage atomicos, outbound_orchestrator.ex:212-236).
- Envio ao subsistema: handler `Monetarie.UseCases.Payments.Outbound.Pix.send_request` -> `Provider.send_pix` -> adapter in-house publica NATS `monetarie.core.pix.payment_request` (`mon/core/backend/lib/monetarie/use_cases/payments/outbound/pix.ex:150-151`; `mon/core/backend/lib/monetarie/services/pix_providers/in_house/adapter.ex:25-31`).
- PIX agendado no Core: `Monetarie.UseCases.Pix.ScheduledPix` (create/cancel/execute_due, `mon/core/backend/lib/monetarie/use_cases/pix/scheduled_pix.ex:53-204`) executado por cron Oban a cada minuto (`mon/core/backend/config/config.exs:105`).

### 2.3 Cabine: recepcao do pedido e debit-then-send (CoreEventProcessor)

Arquivo: `mon/pix/backend/apps/settlement_service/lib/settlement_service/workers/core_event_processor.ex`.

- Consome `monetarie.core.pix.payment_request` (roteado pela chave `"event"`, :72-73).
- E2E fail-closed: para pagamento por CHAVE, o E2E e OBRIGATORIAMENTE o da consulta DICT que a propria cabine fez e cacheou (`Shared.E2eCache.get_cached`, GET sem apagar; cache miss => recusa com `DICT_CONSULT_REQUIRED`, "nada de bloquear saldo nem mandar uma pacs.008 com E2E orfao", :210-231,282-308). O cache so e apagado depois que a transacao foi criada, para retry pos-falha reusar o MESMO E2E (:255-259). Qualquer E2E que o Core mande para pagamento por chave e IGNORADO (:286-290). Isso elimina do caminho do pagamento a consulta DICT online (que na AVIV custa ate 2.5s + ficha do balde).
- Debit-then-send: `check_and_block_balance` cria bloqueio no saldo espelho da Conta PI (ISPB da instituicao) ANTES de qualquer envio (`Balances.create_block`: `available -X`, `blocked +X`, com trava de reserva minima P12, `mon/pix/backend/apps/spi_service/lib/spi_service/balances.ex:210-270`); saldo insuficiente responde erro ao Core sem efeito colateral (:239-278, 1435-1465).
- Montagem: valida a semantica de negocio da pacs.008 ANTES de montar XML (`Pacs008SendValidator`, espelho do ValidaPaymentUseCase do legado; falha => rollback e liberacao do bloqueio, :452-477), monta o XML com dados do recebedor vindos da consulta DICT cacheada (Cdtr completo sem reconsultar o BACEN, :479-524), e publica de forma ATOMICA (outbox ADR-008: `Repo.transaction` + `Publisher.publish_async`) os eventos `transaction.created` e `outbound.send` com o XML (:402-564). Falha em qualquer passo libera o bloqueio (:585-588).

### 2.4 Cabine: envio ao BACEN (OutboundSender)

Arquivo: `mon/pix/backend/apps/spi_service/lib/spi_service/workers/outbound_sender.ex`.

- Consumidor JetStream `monetarie.spi.outbound.>` com `poll_interval: 200ms, batch_size: 100, max_concurrency: 50` (:32-38).
- Cadeia fail-CLOSED em 4 gates antes do POST (:77-101): (1) validacao estrutural do XML (so parse error e terminal; achados estruturais sao deferidos ao gate XSD, :220-256); (2) sancoes em mensagens de dinheiro (pacs.008/004), indisponibilidade da checagem REJEITA fail-closed (:160-166); (3) assinatura XMLDSig com 3 References via HSM RTM, falha de assinatura NUNCA envia (:168-178,258-278); (4) gate XSD OFICIAL BACEN do XML JA ASSINADO, enforce default true (:180-190,280-313).
- ACK HTTP 2xx do ICOM: marca status 2 (ACSP local/enviado) e `confirm_block` do bloqueio PI; explicitamente "ACK HTTP/ICOM NAO liquida a transacao. A liquidacao para o Core so e publicada pelo StatusUpdater ao processar pacs.002 terminal" (:26-29,102-128). Falha de update de status e fail-closed tipado com telemetria (`CRITICAL_STATUS_UPDATE_FAILED`, :405-446).
- Erro de transporte => `{:retry, reason}` (NAK do BaseWorker, redelivery JetStream). Comportamento provado vivo na queda do HSM RTM de 03/07: pagamentos ficaram "Pendente" com bloqueio ativo, NADA saiu sem assinatura, e o reenvio com backoff drenou quando o HSM voltou (handoff `mon/docs/handoff/2026-07-03-pix-hsm-outage-frontend-i18n-icom-handoff.md`, secao 2.2).
- Duplo debito ELIMINADO (Onda 1): o `debit_pi_balance` que debitava `available` uma SEGUNDA vez no aceite foi removido dos 2 callsites do OutboundSender (grep atual: zero ocorrencias em outbound_sender.ex); `create_block` passou a ser o UNICO ponto de saida de `available` no PIX-OUT ("a ser o UNICO ponto de saida de available no PIX-OUT (Debit-Then-Send)", balances.ex:215,119). Diagnostico e magnitude do defeito original: `mon/docs/plans/2026-07-02-ecossistema-best-of-breed-plano-ondas.md:81-93`.

### 2.5 Cabine: leitura do pacs.002 (ICOM proprio) e aplicacao fail-CLOSED

- Recepcao ICOM: `SpiService.Icom.Cpm.Worker`, um GenServer por slot de long-poll no canal primario (CPM), com invariante pre-ACK: o proximo GET (que e o ACK para o BACEN) so sai depois de `AckTracker.persist_batch` confirmar persistencia em banco; queda de Aurora bloqueia o worker e o BACEN retransmite, dedup por MessageId absorve o replay (`mon/pix/backend/apps/spi_service/lib/spi_service/icom/cpm/worker.ex:1-67`). Cadencia: 200ms idle, tick imediato quando ha mensagem, backoff exponencial com alarme apos 10 erros (:74-80 e moduledoc).
- Latencia de leitura PROVADA: READ_LAG (CreDtTm do BACEN -> nosso received_at) = 24-28ms nas mensagens medidas ao vivo em 03/07 (`mon/docs/handoff/2026-07-03-pix-hsm-outage-frontend-i18n-icom-handoff.md`, secao 2.1: "READ_LAG = 24-28 ms nas 6 mensagens"; tambem em `mon/docs/reports/2026-07-04-comparativo-aviv-coreproviders-monetarie.html`: "le a confirmacao pacs.002 em 24 a 28ms").
- `InboundProcessor.process_status_report` (`mon/pix/backend/apps/spi_service/lib/spi_service/workers/inbound_processor.ex:1769-1845`): correlaciona por `OrgnlEndToEndId`/`OrgnlInstrId` (inclusive RtrId "D..." de pacs.004 nossa, :1791-1857), extrai reason_code/description (AB03, ED05...), grava audit inbound e publica o status interno. Mapa canonico: ACSP -> processing, ACCC/ACSC/STLD -> settled (TERMINAL, com prova no exemplo oficial BACEN citada no comentario), RJCT -> rejected, CANC -> cancelled (:1859-1881). Fail-CLOSED: "um pacs.002 de liquidacao/rejeicao nunca pode ser ACKado sem aplicar o status" => erro vira NAK e o JetStream reentrega (:1837-1844).
- `StatusUpdater` (`mon/pix/backend/apps/spi_service/lib/spi_service/workers/status_updater.ex`): guards de estado terminal impedem regressao por pacs.002 fora de ordem (:49-51,643-666). `handle_settlement`: status 4 + historico + `confirm_block` + checagem ANS (SLA BCB de 1600ms) + restituicao de ficha do orcamento DICT client-side (`Shared.Bacen.DictBudget.restitute_on_settled`, :173-231, restituicao :197-200) + publish ATOMICO (outbox) de `monetarie.spi.transaction.settled` para netting e Core (:222-229, contrato :253-275). `handle_rejection`: status 8 + `credit_back_and_release` (estorno do bloqueio PI) + `monetarie.spi.transaction.rejected` atomico para o Core liberar o hold (:290-389). Devolucao de saida (pacs.004 OUTBOUND) tem eventos proprios para nao casar a linha errada por E2E (:216-229,339-398).
- Prova money-safe do estorno: nos 6 RJCT medidos em 03/07, `balance_blocks.status='released'` reason `transaction_rejected` em milissegundos (handoff 2026-07-03, secao 2.1).

### 2.6 Core: desfecho (credito de hold, extrato, materializacao)

- `Monetarie.Infra.Nats.Handlers.PixHandler` consome `monetarie.spi.transaction.*` (`mon/core/CLAUDE.md`, secao NATS). Para outbound rastreado: settled => TB ja foi debitado na iniciacao, nenhuma acao de ledger; grava a linha de extrato (`StatementEntries.record_outbound_settled`, defeito de extrato provado vivo 2026-07-06 citado no comentario) e materializa o status/metadata na `transactions` (`mon/core/backend/lib/monetarie/infra/nats/handlers/pix_handler.ex:194-232,255-307`). rejected => `release_held_funds` libera o hold (:241-247).
- Guard de credito: `handle_transaction_ledger` recusa payload com status nao liquidado (whitelist `settled/STLD/ACSC`, :339-395), coerente com o canonico PIX-in de 09/07.

### 2.7 Devolucao (pacs.004) e vigilancia de dinheiro preso

- Devolucao de saida: `SpiService.Workers.ReturnProcessor` consome `monetarie.spi.return.>`, valida elegibilidade e regras SPI, gera RtrId no formato do XSD (`D + ISPB + YYYYMMDDHHmm + 11 alfanum`), cria bloqueio de saldo e enfileira a pacs.004 no outbound (`mon/pix/backend/apps/spi_service/lib/spi_service/workers/return_processor.ex:1-50`). A confirmacao/rejeicao da pacs.004 e correlacionada pelo RtrId no pacs.002 (inbound_processor.ex:1791-1857) e o Core estorna materializacao orfa via `monetarie.spi.return.rejected` (status_updater.ex:339-390).
- Dinheiro preso: `StuckOutboundChecker` varre a cada 10min pacs.008 OUTBOUND em PDNG/ACSP ha mais de 30min e LOGA alerta agregado; NUNCA muda status ("status final so com evidencia do BACEN") e tambem varre camt.060 sem resposta (`mon/pix/backend/apps/spi_service/lib/spi_service/workers/stuck_outbound_checker.ex:1-42`). O desfecho e decisao do operador.

---

## 3. Diferencas estruturais e por que existem

| Dimensao | AVIV (indireto, OnZ) | Monetarie (direto, SPI) |
|---|---|---|
| Confianca no caminho | OnZ assina/valida; AVIV manda JSON HTTP sem TLS (contexto verificado do mandato; transporte em `onz/client.ex:258-259` + `runtime.exs:956`) | Cabine assina XMLDSig no HSM, mTLS ICP-Brasil, XSD oficial fail-closed (outbound_sender.ex:77-101) |
| Confirmacao | Long-poll na OnZ, mediana 1.6-2.0s p/ settlement de PIX-OUT (pix.ex:1300-1310); LP p50 471ms (poller.ex:106-108) | Long-poll ICOM direto no BACEN; leitura da pacs.002 em 24-28ms provada (handoff 2026-07-03 secao 2.1) |
| DICT no pagamento | Consulta DICT ONLINE encadeada no envio (cache 180s; miss custa a chamada + ficha; timeout ate 5.3s) (adapter.ex:493-560; dict.ex:171-196) | ZERO consulta no envio: E2E e recebedor vem da consulta cacheada previa; sem cache => recusa fail-closed (core_event_processor.ex:210-231) |
| Reserva do dinheiro do cliente | TB pending two-phase por CONTA do cliente, post/void no terminal (pending_transfers.ex) | TB pending por conta no Core (outbound_orchestrator.ex:161-198) + bloqueio no espelho da Conta PI na cabine (balances.ex:210-270) |
| Rate limit externo | Balde DICT do BACEN intermediado pela OnZ, espelho local + fila retry 120min (dict_bucket/*, pix_out_retry_worker.ex) | Participante direto: orcamento DICT proprio (`Shared.Bacen.DictBudget`, restituicao em status_updater.ex:200); consulta ja aconteceu antes do pagamento |
| Composicao | Monolito: 1 processo, pipeline sincrono ate `accepted` (orchestrator.ex) | 2 aneis (Core + cabine) x N workers via NATS JetStream/outbox (BaseWorker poll 200ms em cada salto) |
| Resposta noturna Res.142 | Enforcement REMOVIDO (limit_check.ex:22-26,69) | ENFORCADO 20:00-06:00 BRT (mon limit_check.ex:24-27,76-88) |
| Estorno automatico | on_reply RJCT voida TB na hora; TTL da fila voida em 120min; StaleChecker forca void; VoidGuard consulta o provedor antes de voidar (pending_transfers.ex:236-268) | RJCT estorna bloqueio na hora (status_updater); presos em ACSP: deteccao apenas, desfecho manual (stuck_outbound_checker.ex) |

O motivo de fundo: a AVIV depende de um terceiro no caminho do dinheiro e compensa com maquinaria de resiliencia client-side (fila, balde espelho, FastTrack, StaleChecker, VoidGuard). A Monetarie removeu o terceiro (menos latencia, menos modos de falha externos) e gastou a engenharia em fail-closed regulatorio (assinatura, XSD, sancoes, evidencia BACEN antes de qualquer desfecho).

---

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

1. **VoidGuard antes de estorno** (pending_transfers.ex:236-268). Antes de estornar/liberar bloqueio por timeout/rejeicao tardia, consultar evidencia de liquidacao (no nosso caso: camt.060/053 ou reprocesso do acervo ICOM) e RECUSAR o estorno se o BACEN liquidou. A Monetarie hoje nao tem timeout-void automatico (menos exposta), mas quando implementarmos qualquer desfecho automatico de "preso", este guard e pre-requisito. Esforco: S/M.
2. **Fila de retry persistente com TTL e webhooks de estado** (pix.ex:349-451; pix_out_retry_worker.ex). Hoje nosso equivalente e NAK/redelivery JetStream + backoff, que funciona (prova HSM 03/07) mas nao tem TTL de expiracao, nao tem visibilidade de fila para operacao nem eventos `queued/processing/failed` ao cliente. Port: fila stage-like com TTL configuravel + estorno guiado por VoidGuard + webhook/evento Core. Esforco: M.
3. **Resolucao automatica de "preso" com consulta lateral** (StaleChecker `query_and_resolve` + FastTrack). Nosso `StuckOutboundChecker` so detecta. Um resolvedor automatico que, com evidencia (camt.060/053, acervo ICOM), finalize ACSP velho ou acione operador com contexto pronto reduz dinheiro preso em escala. Manter a regra do dono: desfecho SO com evidencia BACEN. Esforco: M/L.
4. **Kill switch global + teto operacional por transacao** (pix.ex:744-764; orchestrator.ex:337-375). `PIX_OUT_DISABLED` e `PIX_OUT_MAX_AMOUNT_BU` sao defesas de incidente baratas. O Core hoje tem limites por cliente e noturno, mas nao um teto operacional global de emergencia. Esforco: S (quick win).
5. **Marcador `pending_recovery` para erro ambiguo de infra no TB** (orchestrator.ex:648-714). No Core, `create_pending` com `{:error, reason}` faz `Repo.delete(req)` (outbound_orchestrator.ex:191-197): se o timeout ocorreu DEPOIS do TB aplicar o pending, isso estranda a reserva em TB invisivel ao PG (exatamente o incidente AVIV de 2026-07-01, conta 10203). Portar o padrao: nunca deletar em erro ambiguo; deixar stage 0 para um sweeper idempotente com IDs deterministicos. Esforco: S/M. **Este e o achado de risco de dinheiro mais concreto da trilha.**
6. **IDs semanticos TB derivados do E2E (BatchChain)** (pix.ex:96-98,1173-1180). Lookup reverso O(1) de E2E para transfers TB e replays idempotentes por construcao. Util para reconciliacao em volume. Esforco: M.
7. **Reserva de E2E com expiracao** (`DictE2eReservation`, pix.ex:892-916). Nosso E2eCache (Redis, TTL 5min) cumpre o papel, mas a reserva persistida da AVIV tem auditoria de uso (used_at, expires_at) e sobrevive a restart do Redis. Avaliar persistencia do vinculo consulta->pagamento. Esforco: S.
8. **Otimizacao do lock por conta com fases** (runtime.exs:202-236: TB fora do lock, snapshot pre-lock, in_transit por PG). O dado deles: o round-trip TB era 48%/85% do hold do advisory lock (p99 1235ms). Se o Core Monetarie sofrer contencao por conta em volume, este playbook (flags reversiveis, paridade sombra antes de ligar) e o caminho ja desenhado. Esforco: M/L (portar sob flag).

Nao portar: two-phase deposito PIX-IN (proibido no nosso modelo, canonico 09/07), long-poll de provedor (nao temos provedor), balde DICT espelho no formato OnZ (nosso DictBudget ja cobre o papel de participante direto).

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

1. **Latencia de confirmacao uma ordem de grandeza menor**: 24-28ms de leitura da pacs.002 vs p50 471ms/mediana 1.6-2.0s do LP OnZ (handoff 2026-07-03 secao 2.1; comparativo 2026-07-04; pix.ex:1300-1310). Nao ha terceiro no caminho do dinheiro.
2. **Zero consulta DICT no ato do pagamento** com E2E fail-closed da consulta cacheada (core_event_processor.ex:210-308). A AVIV paga ate 2.5s (timeout) + ficha do balde por cache miss no proprio envio.
3. **Cadeia fail-closed regulatoria**: validacao semantica pre-XML (Pacs008SendValidator), sancoes fail-closed, assinatura HSM fail-closed, XSD oficial pos-assinatura fail-closed (outbound_sender.ex:77-191). A AVIV nao valida nada localmente, confia na OnZ.
4. **Outbox atomico em todo money path** (ADR-008; core_event_processor.ex:402-564; status_updater.ex:222-229,318-325). Na AVIV, webhooks e varios efeitos sao `Task.start` best-effort (pix.ex:408-432).
5. **Pre-ACK durability no ICOM**: o ACK ao BACEN so sai apos persistir (cpm/worker.ex:53-62). No LP AVIV o cursor avanca com poison policy (bounded retry), que e mais fraco que reter o ACK.
6. **Guards de estado terminal** contra pacs.002 fora de ordem (status_updater.ex:49-51,643-666) e recusa de credito em status nao liquidado no Core (pix_handler.ex:339-395).
7. **Noturno Res.142 enforcado** (mon limit_check.ex:76-88) onde a AVIV removeu o enforcement (cp limit_check.ex:22-26).
8. **Estorno de rejeicao provado em ms** com evidencia viva (handoff 2026-07-03 secao 2.1) e devolucao pacs.004 com correlacao propria por RtrId que nao contamina o pagamento original (inbound_processor.ex:1791-1857; status_updater.ex:216-229).

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

### P0 (dinheiro/risco imediato ou gargalo estrutural de volume)

1. **Corrigir o tratamento de erro ambiguo do TB no Core** (port do `pending_recovery` da AVIV). Hoje `Repo.delete(req)` em `{:error, reason}` do create_transfers (outbound_orchestrator.ex:191-197) pode estrandar pending TB orfao se o timeout ocorreu pos-aplicacao. Nunca deletar em ambiguidade; sweeper idempotente. Evidencia do modo de falha: incidente AVIV 2026-07-01 documentado em orchestrator.ex:648-663.
2. **Atacar a serializacao do espelho PI na cabine**. Todo PIX-out faz UPDATE na MESMA linha de `monetarie_spi.balances` do ISPB 46026562 (`create_block` subtrai available e soma blocked na linha do ISPB, balances.ex:210-270) + INSERT de block + history. Em volume AVIV (1,53M tx/dia, pico por segundo bem maior), uma unica linha quente serializa o throughput de saida no row lock do Postgres. Opcoes a estudar: contabilidade por deltas (INSERT-only com fold, padrao BalanceCheckpoint ja portado), sharding logico da linha, ou mover a trava PI para conta TB. NAO VERIFICADO empiricamente o ponto de saturacao; medir antes (bench com PerfLog por fase ja existente).
3. **Medir e encurtar os saltos NATS do caminho de iniciacao**. Sao pelo menos 3 consumidores em serie ate o POST no ICOM (CoreEventProcessor, OutboundSender; mais o outbox Oban de cada publish), cada BaseWorker com `poll_interval: 200ms` (outbound_sender.ex:32-38; status_updater.ex:14-20). Pior caso teorico soma centenas de ms antes de chegar ao BACEN, contra o pipeline sincrono da AVIV. Acoes: publicar metricas end2end_ms por fase (PerfLog Task B1 ja instrumenta), avaliar push consumers/poll menor no money path, e um orcamento explicito por fase dentro do ANS de 1.6s.

### P1 (resiliencia operacional em escala)

4. **Fila de retentativa com TTL + VoidGuard + eventos ao Core** para indisponibilidade transitoria (HSM fora, ICOM instavel): port do desenho stage-4 da AVIV (pix.ex:349-451, pix_out_retry_worker.ex) adaptado a nossa regra (desfecho so com evidencia BACEN). Inclui expiracao com estorno seguro do bloqueio PI e do hold do Core.
5. **Resolvedor assistido de pacs.008 presas**: evoluir o StuckOutboundChecker de deteccao para resolucao guiada por evidencia (camt.060/053, acervo ICOM), com trilha de auditoria. Reduz dinheiro preso sem violar a regra do dono.
6. **Kill switch global + teto operacional por transacao no Core** (`PIX_OUT_DISABLED`, `PIX_OUT_MAX_AMOUNT_BU` equivalentes), defesas de incidente baratas ja provadas na AVIV (pix.ex:744-764; orchestrator.ex:337-375).

### P2 (qualidade/observabilidade)

7. **BatchChain/IDs semanticos TB** para lookup reverso E2E->transfers e reconciliacao O(1) em volume (pix.ex:1173-1180).
8. **Playbook de lock por conta sob flag** (TB fora do lock, snapshot pre-lock) como seguro contra contencao futura no Core (runtime.exs:202-236).
9. **Persistir a reserva consulta->pagamento** (equivalente a DictE2eReservation) para auditoria do vinculo E2E e sobrevivencia a restart de Redis (pix.ex:892-916 vs Shared.E2eCache TTL 5min).

## 7. Perguntas abertas / NAO VERIFICADO

1. **Conteudo dos xlsx de evidencia da AVIV** (`dict-bucket-pixout-23min-2026-06-15.xlsx`, `pixout-onz-status-2026-06-01.xlsx`): binarios, nao abertos nesta trilha. A mecanica de balde/fila que explica o titulo esta provada por codigo/doc, mas o episodio "23min" em si nao foi lido.
2. **Latencia sincrona real do POST pacs.008 na OnZ**: os logs de timing existem (orchestrator.ex:60-68, adapter.ex:694-703) mas nenhum numero medido esta no codigo; o que ha de numerico e o LP (p50 471ms PIX-IN) e a mediana 1.6-2.0s de settlement PIX-OUT em comentario. Confirmacao exigiria dados de runtime (fora do escopo desta trilha, regra 2).
3. **Ponto de saturacao do espelho PI** (recomendacao P0-2): a serializacao por linha unica e propriedade do desenho (provada por codigo), mas o TPS em que ela se torna o gargalo NAO foi medido.
4. **Existencia de pacs.028 no catalogo v5.12.1**: o codigo da cabine removeu o handler ("pacs.028 ficticia removida, nao em XSDs raw v5.12 SPI", outbound_sender.ex:10), mas o moduledoc do StuckOutboundChecker ainda menciona "consulta pacs.028" como opcao do operador (stuck_outbound_checker.ex:11-13). Alinhar a documentacao interna com o catalogo.
5. **Latencia fim a fim do caminho Core->cabine->ICOM na Monetarie**: nao ha medicao consolidada publicada (o PerfLog por fase existe desde 04/07). O ANS monitor existe (`Monitoring.AnsMonitor` 1600ms, `mon/pix/CLAUDE.md` secao Supervision Trees; check_ans em status_updater.ex:192), mas o breakdown por salto precisa ser levantado em HML antes da recomendacao P0-3 virar mudanca.
6. **Flags AVIV**: `pix_out_retry_queue_enabled` default false em codigo (runtime.exs:763) porem TRUE em HML/PRD conforme doc revalidado (dict-bucket-strategy.md secao 2, evidencia de 2026-06-04); `pixout_tb_out_of_lock`/`pixout_lookup_prelock`/`pixout_in_transit_pg` default OFF em codigo, estado vivo em PRD NAO VERIFICADO nesta trilha (sem chamadas AWS por regra).
