# Arquitetura da cabine PIX Monetarie

Data: 2026-07-10. Trilha: arquitetura da cabine PIX. Documento produzido por leitura do código (somente leitura), com evidência arquivo:linha. Caminhos relativos à raiz do monorepo `/Users/luizpenha/monetarie`.

Documento irmão: `docs/architecture/2026-07-10-dicionario-dados-pix.md` (dicionário completo dos bancos da cabine).

## 1. Visão geral

A cabine PIX vive em `pix/` e é composta por:

- **Backend**: umbrella Elixir em `pix/backend` com 4 apps (`shared`, `spi_service`, `dict_service`, `settlement_service`), empacotados em UM único release `monetarie_pix` (`pix/backend/Dockerfile:39`), com 3 endpoints Phoenix (portas 4001/4002/4003) e 4 repos Ecto no mesmo BEAM, todos apontando para o mesmo banco Postgres (`mon_pix`), separados por schema Postgres via `@schema_prefix`.
- **Frontend**: `pix/frontend/admin` (Vue 3 + Pinia + Vite + Tailwind, WebSocket Phoenix), servido por nginx na porta 8080 (serviço ECS `pix-admin-ui`).
- **Simulador**: `pix/simulator-frontend` (SPA para o simulador BACEN local, apenas docker-compose, não deployado no ECS greenfield).
- **Sidecar Go**: `pix/services/bacen_mtls_sidecar` (forward proxy mTLS que assina o handshake TLS no HSM RTM, sem chave privada em disco).

Integrações externas:

- **BACEN direto** (sem proxy externo): DICT v2.11.0 (REST + XML assinado XMLDSig) e SPI/ICOM catálogo v5.12.1 (long-poll HTTP multipart), por mTLS ICP-Brasil com `verify_peer` no pool Finch (`pix/backend/apps/shared/lib/shared/application.ex:210-245`).
- **HSM RTM** (`https://monetarie-hsm-hml.priv.rtmcloud.net.br`, HTTPS 443): assinatura XMLDSig e, via sidecar, assinatura do handshake mTLS (`pix/backend/apps/shared/lib/shared/crypto/rtm_hsm.ex`, `pix/services/bacen_mtls_sidecar/main.go`).
- **Core Monetarie via NATS**: dois planos, RPC request/reply (subjects `monetarie.pix.*`, `dict.*.request`) e eventos JetStream (`monetarie.{spi,dict,settlement,core,audit,dlq}.>`) com outbox durável Oban.

Princípios do desenho (provados no código):

1. **Debit-then-send** no PIX-out: bloqueio de saldo na Conta PI antes do envio da pacs.008 (`pix/backend/apps/settlement_service/lib/settlement_service/workers/core_event_processor.ex:239`).
2. **Pre-ACK invariant** na recepção ICOM: nenhuma mensagem é ACKada ao BACEN antes de persistida em `icom_received` (`pix/backend/apps/spi_service/lib/spi_service/icom/csm/worker.ex:54-61,350-351`).
3. **Fail-closed** no money path: assinatura inbound obrigatória (reject DS01), gate XSD pós-assinatura no envio, enum fechado de reject codes na pacs.002.
4. **Canônico PIX-in**: o BACEN liquida ANTES de entregar a pacs.008 ao recebedor, e o recebedor NÃO recebe pacs.002 de volta; o crédito ao cliente é disparado pelo evento `monetarie.spi.transaction.created` consumido pelo Core (`pix/backend/apps/spi_service/lib/spi_service/workers/pix_in_orphan_reconciliation.ex:5-13`).

## 2. Diagrama de componentes

```mermaid
flowchart LR
  subgraph BACEN["BACEN (RSFN)"]
    DICT["DICT v2.11.0<br/>dict.pi.rsfn.net.br"]
    ICOM1["ICOM primario<br/>icom.pi.rsfn.net.br"]
    ICOM2["ICOM secundario<br/>icom-sec.pi.rsfn.net.br"]
    ARQ["ARQ (arquivos CID)<br/>arq.pi.rsfn.net.br:1130"]
  end

  subgraph HSM["RTM CloudHSM (443)"]
    KMIP["KMIP REST<br/>sign-rsa RSAHASH"]
  end

  subgraph PIXAPI["pix-api (release monetarie_pix, Fargate)"]
    subgraph SPI["spi_service :4002"]
      ICOMSUP["Icom.ApplicationSupervisor<br/>Cpm/Csm Coordinator + 6 slots"]
      INB["InboundProcessor"]
      OUTB["OutboundSender"]
      STUP["StatusUpdater"]
      RETP["ReturnProcessor"]
      MED["Med.* (cautelar, chain block)"]
      BAL["Balances (Conta PI)"]
      CGH["CoreGatewayHandler (RPC)"]
    end
    subgraph DCT["dict_service :4001"]
      KEYS["Keys / Claims / Infractions"]
      CID["CidSyncService"]
      ISYNC["Sync.InboundSync (MED poll)"]
    end
    subgraph SETT["settlement_service :4003 (API gateway)"]
      GW["Router: proxy DICT + SPI"]
      CEP["CoreEventProcessor"]
      SOW["SettlementObligationWorker"]
      QR["QRCodes / CobV / JWS"]
      ACC["Accounting (COSIF)"]
      MON["Monitor + MonitoringChannel WS"]
    end
    SHARED["shared: Client Finch mTLS, XmlSigner/Verifier,<br/>MessageBuilder/Parser, SpiXsd, JetStream, Oban outbox"]
  end

  SIDECAR["bacen_mtls_sidecar (Go)<br/>:9101 dict, :9103 icom, :9104 icom-sec, :9105 arq"]
  NATS[("NATS JetStream<br/>MONETARIE_SPI / DICT / SETTLEMENT / CORE / AUDIT / DLQ")]
  CORE["Core Monetarie (core-api)"]
  PG[("PostgreSQL mon_pix<br/>12 schemas")]
  UI["pix-admin-ui (Vue, nginx :8080)"]

  ICOMSUP -- "GET /out long-poll" --> ICOM1
  ICOMSUP -- failover --> ICOM2
  OUTB -- "POST /api/v1/in/{ispb}/msgs" --> ICOM1
  KEYS -- "GET/POST /entries, /claims, /funds-recoveries" --> DICT
  CID -- "/cids/files /cids/events" --> DICT
  CID -- "download arquivo CID" --> ARQ
  SHARED -- "assinatura XMLDSig" --> KMIP
  SIDECAR -- "mTLS originado no HSM" --> BACEN
  PIXAPI -. "opcional: URLs apontando p/ localhost:910x" .-> SIDECAR
  PIXAPI <--> NATS
  CORE <--> NATS
  PIXAPI --> PG
  UI -- "/api -> :4003, /socket WS" --> SETT
```

## 3. Apps do umbrella e responsabilidades

| App | Porta | Endpoint Phoenix | Responsabilidade | Repo Ecto (schemas Postgres) |
|---|---|---|---|---|
| `spi_service` | 4002 | `SpiServiceWeb.Endpoint` | Liquidação SPI: recepção ICOM (long-poll), InboundProcessor, OutboundSender, StatusUpdater, ReturnProcessor, saldos/bloqueios da Conta PI, MED lado SPI, limites (Res. BCB 402), recorrências (Pix Automático), conciliação diária, RPC `monetarie.pix.*` | `SpiService.Repo` (`monetarie_spi`) |
| `dict_service` | 4001 | `DictServiceWeb.Endpoint` | DICT: chaves, claims/portabilidade, MED 2.0 REST (infrações, refunds, funds recovery, tracking graph, fraud markers), CID sync, poll inbound com watermark, rate limit/buckets | `DictService.Repo` (`monetarie_dict`) |
| `settlement_service` | 4003 | `SettlementServiceWeb.Endpoint` | API gateway único da cabine (proxy p/ DICT e SPI), CoreEventProcessor (eventos do Core), SettlementObligationWorker (instrui crédito no Core), QR/CobV/JWS, tarifas, contabilidade COSIF, relatórios, APIX001, Monitor + WebSocket, DLQ admin | `SettlementService.Repo` (`monetarie_settlement`) |
| `shared` | (lib) | (sem endpoint) | Transporte BACEN (Finch mTLS, `Shared.Bacen.Client`), ISO 20022 (MessageBuilder/MessageParser/SpiXsd/RejectCodes), crypto (XmlSigner/XmlVerifier/RtmHsm/CertificatePool/CertMaterializer), NATS JetStream + outbox Oban, auth/RBAC, auditoria, simulador BACEN, PartitionManager | `Shared.Repo` (`monetarie_auth`, `monetarie_audit`, `monetarie_spi*`, `bacen_simulator`, `monetarie_webhook`) |

Evidência dos repos: `pix/backend/config/config.exs:8,36,54,119`; release único: `pix/backend/Dockerfile:39,77-84`.

## 4. Pipeline de mensagens ISO 20022

### 4.1 Recepção ICOM (long-poll, NÃO webhook)

- Toggle de fonte: `INBOUND_SOURCE` (`icom_http` liga o poll real; `bacen_mock_nats` é o default sem BACEN), lido em `pix/backend/apps/spi_service/lib/spi_service/icom/application_supervisor.ex:69-99`.
- Leader election cluster-wide por advisory lock Postgres (`pg_try_advisory_lock`), um Coordinator por canal (CPM primário, CSM secundário): `pix/backend/apps/spi_service/lib/spi_service/icom/csm/coordinator.ex:287`. O líder sobe até 6 workers (slots), teto do BACEN, configurável por `ICOM_MAX_SLOTS` (em produção 4, deixando 2 conexões livres).
- Loop do worker: `GET /api/v1/out/{ispb}/stream/start` e depois GETs sucessivos na URL do header `PI-Pull-Next` (`pix/backend/apps/spi_service/lib/spi_service/icom/csm/worker.ex:272-331`; endpoints em `pix/backend/apps/shared/lib/shared/bacen/client.ex:298-361`). Timeout de long-poll 60s (`client.ex:57`). 204 sem mensagens: cadência 3000ms no CSM e 200ms no CPM. Erro de transporte: backoff exponencial com jitter, cap 30s, alarme após 10 falhas.
- **Invariante pre-ACK**: o próximo GET (que funciona como ACK do lote anterior) só sai depois de `AckTracker.persist_batch` gravar todas as parts em `monetarie_spi.icom_received` (insert idempotente por `message_id`, `pix/backend/apps/spi_service/lib/spi_service/icom/ack_tracker.ex:82-88`).
- Estado durável de sessão em `monetarie_spi.icom_sessions` (token, `last_pull_next`, índice único parcial de 1 sessão `open` por slot), permitindo retomada após crash/failover.
- `NatsBridge` publica cada part em `monetarie.spi.inbound.<msg_type>` (`pix/backend/apps/spi_service/lib/spi_service/icom/nats_bridge.ex:55,130-148`), com republicação de pendentes no recovery.
- Saúde: `Heartbeat` ETS por tick + `HealthMonitor` (watchdog de slot preso) + `IcomLagMonitor` (p95 do lag CreDtTm até received_at contra ANS 1600ms, `pix/backend/apps/settlement_service/lib/settlement_service/monitoring/icom_lag_monitor.ex`).
- Probe de eco: `EchoProbeWorker` envia pibr.001 por canal a cada 5 min (flag `ECHO_PROBE_ENABLED`), persiste RTT em `monetarie_spi.echo_probes` e alimenta o circuit breaker `Shared.Bacen.ChannelRouter` que decide failover primário/secundário (`pix/backend/apps/settlement_service/lib/settlement_service/monitoring/echo_probe_worker.ex:128-158`).

### 4.2 InboundProcessor

`pix/backend/apps/spi_service/lib/spi_service/workers/inbound_processor.ex` (consumer durável `inbound-processor`, stream `MONETARIE_SPI`, filter `monetarie.spi.inbound.>`, linhas 79-86).

- Classificação por tipo no dispatcher (`dispatch_message/2`, linhas 172-211): pacs.008, pacs.002, pacs.004, camt.014/025/029/052/053/054/055, admi.002/004, pibr.002, pain.014, reda.016/017/041. Autodetecção por elemento raiz quando só há XML (`MessageParser.detect_type`, `pix/backend/apps/shared/lib/shared/bacen/iso20022/message_parser.ex:73-110`).
- Gates pré-dispatch: verificação de assinatura XMLDSig fail-closed para os tipos de dinheiro (`@signature_required_types`, linhas 305-308; falha rejeita com DS01) e validação estrutural advisory da pacs.008 (linhas 272-296).
- Dedup em 2 camadas: Redis SET-NX `pix:dedup:<resource_id>` TTL 3600s fail-open (linhas 125-158) e dedup transacional no banco (`upsert_inbound_in_tx`, linhas 868-902).
- Persistência atômica em `monetarie_spi.bacen_inbound` (XML bruto + status) e `monetarie_spi.messages`.
- Rejeição de pacs.008 recebida: decisão pura em `inbound_outcome` (linhas 465-587), registro e emissão de pacs.002 RJCT com ACK (nunca NAK, para não virar poison; linhas 1038-1098).

### 4.3 Envio outbound

- A "fila" de envio é NATS (`monetarie.spi.outbound.>`), alimentada por outbox durável: `Shared.Nats.Publisher.publish_async/2` enfileira um job Oban dentro da mesma `Repo.transaction` do estado de negócio (`pix/backend/apps/shared/lib/shared/nats/publisher.ex:69-99`).
- `OutboundSender` (`pix/backend/apps/spi_service/lib/spi_service/workers/outbound_sender.ex:30-38`, consumer `outbound-sender`): pipeline fail-closed valida estrutura, sanções, assina XMLDSig (`sign_outbound_xml`, linha 271), valida o XML ASSINADO contra o XSD (gate `PIX_XSD_GATE_ENFORCE`, default true, linhas 294-311) e faz `POST /api/v1/in/{ispb}/msgs` no ICOM (linha 455). ACK HTTP 2xx marca ACSP e confirma o bloqueio de saldo; ACK ICOM não é liquidação.
- `monetarie_spi.bacen_outbound` é acervo/auditoria de tudo que foi enviado (gravado por `Shared.Audit.BacenLogger`, `pix/backend/apps/shared/lib/shared/audit/bacen_logger.ex:322`), não é a fila.
- Retry/DLQ/poison: `Shared.Workers.BaseWorker` faz NAK com backoff exponencial e `max_deliver` 5; esgotado, publica em `monetarie.dlq.<subject original>` (`pix/backend/apps/shared/lib/shared/workers/base_worker.ex:605-649`). `Shared.Workers.PoisonPolicy` detecta mensagem venenosa e publica em `monetarie.dlq.poison.<consumer>` com ACK (nunca drop silencioso). Admin de DLQ: `pix/backend/apps/settlement_service/lib/settlement_service_web/controllers/admin/dlq_controller.ex` (consumers `dlq-admin-reader`/`dlq-replay`); profundidade monitorada por `dlq_depth_monitor.ex`.
- `StuckOutboundChecker` (GenServer, a cada 10 min) detecta pacs.008 OUTBOUND paradas em PDNG/ACSP há mais de 30 min e camt.060 sem resposta, gera 1 WARNING agregado e nunca altera status (`pix/backend/apps/spi_service/lib/spi_service/workers/stuck_outbound_checker.ex:36-38,128-180`).

### 4.4 XMLDSig e XSDs

- Assinatura: `Shared.Crypto.XmlSigner` (`pix/backend/apps/shared/lib/shared/crypto/xml_signer.ex`), `sign_spi/2` com 3 References (KeyInfo, AppHdr, Document) e `sign_dict/2` com 2, Exclusive C14N + RSA-SHA256. Bifurcação HSM: `rsa_sign/3` usa `RtmHsm.sign_base64(ispb, data, :sha256)` quando `RTM_HSM_ENABLED=true`, senão assina em software (linhas 358-368). Com HSM ligado a chave privada de assinatura NUNCA sai do HSM (`Shared.Crypto.CertificatePool.load_signing_key/1`, `pix/backend/apps/shared/lib/shared/crypto/certificate_pool.ex:194-207`).
- Verificação inbound: `Shared.Crypto.XmlVerifier` valida digests e assinatura com o certificado do BACEN (PEERPIA) do `CertificatePool` (`pix/backend/apps/shared/lib/shared/crypto/xml_verifier.ex:32-140`).
- `xmllint` (pacote `libxml2-utils`, instalado no runtime do Docker, `pix/backend/Dockerfile:47`) é usado em 3 pontos: canonicalização Exclusive C14N (`pix/backend/apps/shared/lib/shared/crypto/exc_c14n.ex:37`), normalização do documento a assinar (`xml_signer.ex:272`) e validação contra XSD (`pix/backend/apps/shared/lib/shared/validation/spi_xsd.ex:92`).
- XSDs do catálogo: `pix/backend/apps/shared/priv/xsd/spi/v5.12.1/` e `v5.11.1/`, 28 arquivos `.xsd` em cada (pacs.008.spi.1.15, pacs.002.spi.1.16, pacs.004.spi.1.5, camt.052/053/054/055/060, camt.014/025/029, admi.002/004, pain.009/011/012/013/014, pibr.001/002, reda.014/016/017/022/031/041, trck.002, xmldsig). O validador `Shared.Validation.SpiXsd` monta um driver-schema temporário (importa o XSD oficial + `xmldsig-permissive.xsd`) e roda `xmllint --noout --schema` (`spi_xsd.ex:50-141`). Versão por tipo com override `SPI_VERSION_OVERRIDES`.
- A antiga tabela `monetarie_spi_ref.xsd_schemas` (placeholders) foi REMOVIDA (`pix/backend/apps/shared/priv/repo/migrations/20260426170000_drop_xsd_schemas_table.exs`).

### 4.5 RejectCodes

`Shared.Bacen.Iso20022.RejectCodes` (`pix/backend/apps/shared/lib/shared/bacen/iso20022/reject_codes.ex:26-32`): enum fechado com **43 códigos** do `ExternalStatusReason1Code` da pacs.002.spi.1.16 (AB03, AB09, AB11, AC03, AC06, AC07, AC14, AG03, ..., DUPL, ED05, FRAD, MD01, SL02, UPAY). `sanitize_pacs002/1` (linhas 55-60) é o gate de build da pacs.002: código fora do enum é substituído pelo fallback `ED05` com log crítico, evitando pacs.002 XSD-inválida (causa do loop NAK/poison do incidente de 2026-07-09). Uso no InboundProcessor: `inbound_processor.ex:2649`.

### 4.6 Tipos de mensagem: builders e handlers

| Mensagem | Builder (envio) | Handler (recepção) |
|---|---|---|
| pacs.008 | `MessageBuilder.build/2` (`pix/backend/apps/shared/lib/shared/bacen/iso20022/message_builder.ex:148`) | `process_incoming_payment` (`inbound_processor.ex:404`) |
| pacs.002 | `message_builder.ex:268` (resposta ACSP/RJCT do PIX-in) | `process_status_report` (delega ao StatusUpdater) |
| pacs.004 | `message_builder.ex:322` (via ReturnProcessor) | `process_incoming_return` (`inbound_processor.ex:1103`) |
| camt.052 | `message_builder.ex:432` | `process_account_report` (`inbound_processor.ex:1976`) |
| camt.053 | `message_builder.ex:455` | `process_eod_statement` (`inbound_processor.ex:2167`) |
| camt.054 | `message_builder.ex:492` | `process_notification` (`inbound_processor.ex:1219`), correlaciona com camt.060 |
| camt.060 | `Shared.Bacen.SpiClient` (`pix/backend/apps/shared/lib/shared/bacen/spi_client.ex:565-574`); rastreio em `monetarie_spi.camt060_requests` | resposta chega como camt.052/053/054 pela stream |
| pibr.001 (eco) | `SpiClient.build_pibr001_request/2` (`spi_client.ex:594`), enviado por `SpiClient.echo/1` (`spi_client.ex:336`) | pibr.002: `process_echo_response` (`inbound_processor.ex:2499`) |
| admi.002/004 | `message_builder.ex:1115` | `process_system_event` (`inbound_processor.ex:1339`) |
| reda.016/017/041 | `message_builder.ex:1433,1467,1516` (participantes indiretos) | `process_party_status_advice`/`process_party_report`/`process_party_activity_advice` (`inbound_processor.ex:1561,1702,1739`) |

## 5. Fluxo PIX-out (nós pagamos: pacs.008 enviada, pacs.002 recebida)

```mermaid
sequenceDiagram
  autonumber
  participant IB as IB / Core (core-api)
  participant N as NATS JetStream
  participant CEP as CoreEventProcessor (settlement)
  participant BAL as Balances (Conta PI)
  participant OS as OutboundSender (spi)
  participant HSM as HSM RTM
  participant BC as BACEN SPI (ICOM)
  participant IP as InboundProcessor (spi)
  participant SU as StatusUpdater (spi)

  IB->>N: monetarie.core.pix.payment_request (evento)
  N->>CEP: consumer core-event-processor (monetarie.core.pix.>)
  CEP->>CEP: resolve E2E do cache da consulta DICT (fail-closed se expirou)
  CEP->>BAL: create_block (DEBIT-THEN-SEND, balance_blocks)
  CEP->>CEP: cria transaction em monetarie_spi.messages (PDNG, OUTBOUND)
  CEP->>N: monetarie.spi.transaction.created + monetarie.spi.outbound.send (outbox Oban, mesma tx)
  N->>OS: consumer outbound-sender (monetarie.spi.outbound.>)
  OS->>HSM: assina XMLDSig (sign-rsa RSAHASH)
  OS->>OS: gate XSD do XML assinado (fail-closed)
  OS->>BC: POST /api/v1/in/{ispb}/msgs (pacs.008, mTLS)
  BC-->>OS: 2xx ACK de transporte
  OS->>BAL: confirm_block (ACK nao e liquidacao)
  BC->>BC: liquida na Conta PI (ou rejeita)
  BC-->>IP: pacs.002 (ACSP/ACSC/RJCT) via long-poll GET /out
  IP->>N: monetarie.spi.transaction.<status> (STATUS_UPDATED)
  N->>SU: consumer status-updater (monetarie.spi.transaction.>)
  alt liquidado (ACSC/ACCC/STLD)
    SU->>N: monetarie.spi.transaction.settled
    N->>IB: Core marca a saida liquidada (comprovante)
  else rejeitado (RJCT)
    SU->>BAL: revert_block (credit-back)
    SU->>N: monetarie.spi.transaction.rejected (Core libera hold)
  end
```

Passos com evidência: request do Core roteado por campo `event` (`core_event_processor.ex:13,72,190`); bloqueio antes do envio (`core_event_processor.ex:239`; `pix/backend/apps/spi_service/lib/spi_service/balances.ex:210`); publicação atômica do par created + outbound.send (`core_event_processor.ex:425,544`); pipeline do OutboundSender (`outbound_sender.ex:91-124`); mapeamento TxSts da pacs.002 (`inbound_processor.ex:1770-1881`); liquidação, guards de idempotência e credit-back (`status_updater.ex:173-275,330-429,683-704`). Timeout nunca finaliza OUTBOUND (o SPI pode ter liquidado; `status_updater.ex:577-620`).

## 6. Fluxo PIX-in (nós recebemos: BACEN liquida ANTES; recebedor NÃO recebe pacs.002)

```mermaid
sequenceDiagram
  autonumber
  participant BC as BACEN SPI (ICOM)
  participant W as Icom Worker (long-poll)
  participant AT as AckTracker (icom_received)
  participant N as NATS JetStream
  participant IP as InboundProcessor (spi)
  participant CORE as Core (core-api)
  participant BAL as Balances (Conta PI)

  Note over BC: BACEN JA LIQUIDOU na Conta PI<br/>antes de entregar a pacs.008
  W->>BC: GET stream/start + GET PI-Pull-Next (long-poll)
  BC-->>W: 200 multipart (pacs.008 recebida)
  W->>AT: persist_batch (pre-ACK invariant)
  W->>BC: proximo GET (= ACK do lote)
  AT->>N: monetarie.spi.inbound.pacs.008 (NatsBridge)
  N->>IP: consumer inbound-processor (monetarie.spi.inbound.>)
  IP->>IP: verifica XMLDSig (fail-closed, DS01) + XSD advisory + dedup
  IP->>CORE: Gnat.request monetarie.core.pix.validate_account (RPC)
  IP->>IP: SPI + sancoes + conta + gate de QR (txid)
  alt valido
    IP->>BAL: credita Conta PI (update_pi_balance :credit)
    IP->>N: monetarie.spi.transaction.created (direction INBOUND, outbox na mesma tx)
    N->>CORE: PixHandler credita o CLIENTE (ledger TB + extrato)
    IP->>BC: pacs.002 ACSP (unica resposta; nao ha pacs.002 de volta)
    IP->>IP: advance_inbound_credit_to_accc (marca ACCC local)
  else invalido
    IP->>BC: pacs.002 RJCT (codigo do enum RejectCodes, fallback ED05)
    Note over IP: rejeicao NUNCA credita
  end
  Note over IP: creditos inaplicaveis (AC03/AC06/AC07/AC14/AG03)<br/>creditam a PI e disparam devolucao automatica pacs.004
```

Pontos canônicos com evidência:

- O BACEN liquida antes de entregar e o recebedor não recebe pacs.002: comentários e código em `inbound_processor.ex:947-952` (marca ACCC localmente exatamente porque não virá pacs.002) e `pix_in_orphan_reconciliation.ex:5-13`.
- Evento pro Core publicado DENTRO da transação de banco (outbox): `inbound_processor.ex:2866-2894`, payload com `direction: "INBOUND"`, `amount`, `end_to_end_id`, `account_id`, `trace_id`.
- Resposta ACSP ao BACEN só depois do evento garantido: `inbound_processor.ex:928-938`.
- Flag `TWO_PHASE_PIX_IN` (evento `.received` + gatilho `settled`) existe mas é PROIBIDA em ambientes reais: o gatilho settled só existe no simulador; ligar significa cliente nunca creditado (regra do projeto, `runtime.exs:24`).
- Órfãos: `PixInOrphanReconciliation` (Oban cron) cruza camt.054 CRDT (prova de liquidação do BACEN) com `messages`/outbox e persiste divergências em `monetarie_spi.pix_in_orphans`; republicação automática só com `PIX_IN_ORPHAN_AUTO_REPUBLISH=true` (default OFF) e só para o caso `no_outbox_event` (`pix_in_orphan_reconciliation.ex:230-248,369-414`).
- Devolução automática de crédito inaplicável: `process_credit_returned` publica `monetarie.spi.return.created`, e o `ReturnProcessor` monta a pacs.004 (`inbound_processor.ex:709-826`; `return_processor.ex:184-209`).
- O WAL de crédito do PIX-in (`pix_in_credit_wal`) fica no Core, não na cabine.

## 7. DICT (v2.11.0)

- **Cliente**: `Shared.Bacen.DictClient` (`pix/backend/apps/shared/lib/shared/bacen/dict_client.ex`). `get_entry/2` faz `GET /entries/{Key}` e injeta `PI-EndToEndId` obrigatório (gerado por `MessageBuilder.generate_e2e_id/1` se ausente, linhas 123-146; formato E2E: `E` + ISPB(8) + `AAAAMMDDHHmm` + 11 alfanuméricos). Headers `PI-RequestingParticipant`/`PI-PayerId` em `pix/backend/apps/shared/lib/shared/bacen/client.ex:654-698`. O E2E da consulta é cacheado (`Shared.E2eCache`) e reutilizado na pacs.008 do PIX-out.
- **Modos**: dois eixos independentes. `DICT_EXTERNAL_MODE` (`local`/`mirror`/`bacen`, `runtime.exs:109-124`) e o adapter efetivo `:dict_service, :bacen_adapter` (`Real` delega ao DictClient, `Simulator` responde pelo schema `bacen_simulator`, `Local` sempre erro; `pix/backend/apps/dict_service/lib/dict_service/bacen_adapter.ex:50-84`).
- **Registro/exclusão de chave**: `DictService.Keys.create_entry/1` chama `POST /entries/` assinado; `delete_entry/3` chama `POST /entries/{Key}/delete` (não HTTP DELETE); tudo auditado em `monetarie_dict.dict_external_operations` e publicado em `monetarie.dict.keys.{created,deleted,blocked,unblocked}` (`pix/backend/apps/dict_service/lib/dict_service/keys.ex:69,300,384,453,726-865`).
- **CID sync**: um único GenServer `DictService.Sync.CidSyncService` com dois timers, full sync 6h (`POST /cids/files`, poll até `AVAILABLE`, download via pool `arq-h` porta 1130, SHA-256 + gunzip, diff contra `monetarie_dict.keys`) e event poll 5 min (`GET /cids/events?Participant=&KeyType=...` com paginação). Flags `CID_FULL_SYNC_ENABLED`, `CID_EVENT_POLL_ENABLED`, `CID_ENTRY_INGEST_ENABLED` (default false no código, ligadas por env no ECS). Verificador de sincronismo por XOR dos CIDs ativos confirmado via `POST /sync-verifications/` (`pix/backend/apps/dict_service/lib/dict_service/sync/cid_sync_service.ex:26-33,189-272,511-559,606-826`).
- **Claims**: prazos 7 dias (notificação ao doador), 14 dias (auto-confirmação) e 30 dias (expiração) (`pix/backend/apps/dict_service/lib/dict_service/claims.ex:43-45`); rotas 2.11.0 `POST /claims/`, `GET /claims/{ClaimId}`, ações por sufixo (`dict_client.ex:369-515`); timers duráveis via JetStream com `Nats-Msg-Deliver-After` e consumer `ClaimDeadlineConsumer`.
- **MED/infrações, duas metades**: (a) REST DICT no `dict_service`: `infraction_reports`, `refund_requests`, `funds_recoveries`, `tracking_graphs` (endpoints `POST /infraction-reports/`, `POST /funds-recoveries/{id}/refund`, `GET /funds-recoveries/{id}/tracking-graph`; `dict_client.ex:534-965`); (b) lado SPI no `spi_service`: `SpiService.Med` com bloqueio cautelar de 72h, bloqueio em cadeia até 5 hops (`ChainBlocker`), timers de 30 min/7 dias/90 dias (`TimerEnforcer`), resolução via camt.029.
- **Sincronização inbound MED** com marca d'água durável em `monetarie_dict.dict_sync_cursors` (poll de refunds, infrações, claims donor/claimer; fraude é manual porque o BACEN rejeita `GET /fraud-markers?ModifiedAfter`): `pix/backend/apps/dict_service/lib/dict_service/sync/inbound_sync.ex:30-78,244-285`.
- **Pontes com o Core/SPI**: infração recebida publica `monetarie.dict.med.infraction_received` (`pix/backend/apps/dict_service/lib/dict_service/infractions.ex:731`), consumida por `SpiService.Med.InfractionResponder` (abre claim de fraude + cautelar); camt.029 recebida publica `monetarie.spi.med.resolution_received`, consumida por `ResolutionConsumer` (libera cautelar e chain blocks).
- **Ownership por OTP**: `monetarie_dict.ownership_validations` guarda só o hash SHA-256 do código; gate `DICT_OWNERSHIP_ENFORCED` (default off).

## 8. Gateway request/reply com o Core

O módulo Gateway vive no Core; a cabine expõe os responders:

- **RPC síncrono** `SpiService.Nats.CoreGatewayHandler` (`pix/backend/apps/spi_service/lib/spi_service/nats/core_gateway_handler.ex:39-48`), payloads `:erlang.term_to_binary`, reply para `reply_to`: `monetarie.pix.limits.check`, `monetarie.pix.limits.get`, `monetarie.pix.idempotency.check`, `monetarie.pix.e2e.generate`, `monetarie.pix.e2e.parse`, `monetarie.pix.med.check`, `monetarie.pix.qrcode.image`, `monetarie.pix.qrcode.cobv` (motor CobV completo: EMV + calculadora de encargos + JWS; linhas 257-380).
- **RPC DICT**: `dict.lookup.request` e `dict.list_keys.request` (`pix/backend/apps/spi_service/lib/spi_service/nats/dict_lookup_responder.ex:15-16`), `dict.api.request` (`dict_api_responder.ex:16`).
- **Health**: `pix.health.check` (`health_responder.ex:31`).
- **RPC no sentido inverso** (cabine pergunta ao Core): `Gnat.request` em `monetarie.core.pix.validate_account` na validação de conta do PIX-in (`inbound_processor.ex:114,988`).
- **Eventos assíncronos Core -> cabine**: `CoreEventProcessor` consome `monetarie.core.pix.>` (consumer `core-event-processor`) e roteia pelo campo `event`: `payment_request`, `return_request`, `key_create`, `key_delete`, `dict_lookup`, `balance_inquiry`, `claim_*`, `recovery_*`, `infraction_*` (`core_event_processor.ex:10-148`). O embrulho `%{"source","data":{"event"}}` é normalizado na entrada (linha 68).

## 9. Subjects NATS (exatos, com evidência)

Streams JetStream (definidos em `pix/backend/apps/shared/lib/shared/nats/jetstream.ex:25-98`): `MONETARIE_SPI` (`monetarie.spi.>`, 7d/10GB), `MONETARIE_DICT` (`monetarie.dict.>`), `MONETARIE_SETTLEMENT` (`monetarie.settlement.>`), `MONETARIE_AUDIT` (`monetarie.audit.>`, 90d), `MONETARIE_DLQ` (`monetarie.dlq.>`, 90d), `MONETARIE_CORE` (`monetarie.core.>`). Réplicas via `NATS_STREAM_REPLICAS`, teto via `NATS_STREAM_MAX_BYTES` (`jetstream.ex:512,519`).

### 9.1 Consumidos pela cabine (consumers duráveis, push)

| Subject (filter) | Stream / consumer | Evidência |
|---|---|---|
| `monetarie.spi.inbound.>` | MONETARIE_SPI / `inbound-processor` | `inbound_processor.ex:79-86` |
| `monetarie.spi.outbound.>` | MONETARIE_SPI / `outbound-sender` | `outbound_sender.ex:32-38` |
| `monetarie.spi.transaction.>` | MONETARIE_SPI / `status-updater` | `status_updater.ex:14-20` |
| `monetarie.spi.return.>` | MONETARIE_SPI / `return-processor` | `return_processor.ex:14-20` |
| `monetarie.spi.transaction.settled` | MONETARIE_SPI / `settlement-obligation-worker` | `settlement_obligation_worker.ex:45-49` |
| `monetarie.spi.med.resolution_received` | MONETARIE_SPI / `med-resolution-consumer` | `resolution_consumer.ex:21-22` |
| `monetarie.spi.recurrence.execute` | MONETARIE_SPI / execution worker | `execution_worker.ex:30` |
| `monetarie.core.pix.>` | MONETARIE_CORE / `core-event-processor` | `core_event_processor.ex:42-44` |
| `monetarie.settlement.schedule.>` | MONETARIE_SETTLEMENT / `settlement-scheduler` | `scheduler.ex:17-18` |
| `monetarie.settlement.file.>` | MONETARIE_SETTLEMENT / `settlement-file-importer` | `file_importer.ex:18` |
| `monetarie.dict.infractions.response` | MONETARIE_DICT / `med-infraction-response` | `infraction_response_consumer.ex:30-31` |
| `monetarie.dict.deadlines.>` | MONETARIE_DICT / claim deadlines | `claim_deadline_consumer.ex:27` |
| `monetarie.dlq.>` | MONETARIE_DLQ / `dlq-admin-reader`, `dlq-replay` | `dlq_controller.ex:16,42-53`; `dlq_depth_monitor.ex:38` |

RPC assinados por `Gnat.sub` (sem stream): `monetarie.pix.*` (8 subjects, `core_gateway_handler.ex:39-48,74`), `dict.lookup.request`/`dict.list_keys.request` (`dict_lookup_responder.ex:15-16,33-34`), `dict.api.request` (`dict_api_responder.ex:16,33`), `pix.health.check` (`health_responder.ex:31,47`).

### 9.2 Publicados pela cabine (principais)

| Subject | Quem publica | Evidência |
|---|---|---|
| `monetarie.spi.inbound.<msg_type>` | NatsBridge (ICOM -> NATS) | `nats_bridge.ex:55,130-148` |
| `monetarie.spi.transaction.created` | InboundProcessor (PIX-in), CoreEventProcessor (PIX-out), reconciliação de órfãos | `inbound_processor.ex:2874`; `core_event_processor.ex:425`; `pix_in_orphan_reconciliation.ex:382` |
| `monetarie.spi.transaction.received` | InboundProcessor (SÓ com flag two-phase, proibida) | `inbound_processor.ex:2858-2864` |
| `monetarie.spi.transaction.settled` | StatusUpdater (pacs.002 terminal) | `status_updater.ex:225` |
| `monetarie.spi.transaction.rejected` | InboundProcessor, StatusUpdater | `inbound_processor.ex:1078`; `status_updater.ex:406` |
| `monetarie.spi.transaction.returned` | CoreEventProcessor (devolução pedida pelo Core) | `core_event_processor.ex:693` |
| `monetarie.spi.transaction.ab03_rejection` | StatusUpdater | `status_updater.ex:1057` |
| `monetarie.spi.outbound.send` | CoreEventProcessor, ExecutionWorker (recorrência) | `core_event_processor.ex:544`; `execution_worker.ex:308` |
| `monetarie.spi.outbound.return` | ReturnProcessor (pacs.004 pronta) | `return_processor.ex:208` |
| `monetarie.spi.outbound.result` | OutboundSender | `outbound_sender.ex:475` |
| `monetarie.spi.return.created` | InboundProcessor (devolução automática), consumidor de infração | `inbound_processor.ex:759`; `infraction_response_consumer.ex:162,200` |
| `monetarie.spi.return.rejected` | StatusUpdater | `status_updater.ex:399` |
| `monetarie.spi.med.resolution_received` | InboundProcessor (camt.029) | `resolution_consumer.ex:6` |
| `monetarie.spi.reconciliation.mismatch` | StatusUpdater | `status_updater.ex:656` |
| `monetarie.spi.statement.report_available` / `.eod_received` | InboundProcessor (camt.052/053) | `inbound_processor.ex:2058,2232` |
| `monetarie.spi.recurrence.{created,updated,paused,resumed,cancelled,execute}` | Recurrences | `recurrences.ex:76,200,267,309,363,654` |
| `monetarie.spi.recurrence.pre_debit_notification` / `.activation_status` | NotificationWorker; InboundProcessor (pain.014) | `notification_worker.ex:211`; `inbound_processor.ex:2315` |
| `monetarie.spi.participant.directory_update` | InboundProcessor (reda) | `inbound_processor.ex:2379` |
| `monetarie.settlement.transaction.credited` | SettlementObligationWorker (instrui o crédito no Core) | `settlement_obligation_worker.ex:65,373` |
| `monetarie.settlement.session.{created,opened,closed,finalized}` | Sessions | `sessions.ex:52,95,147,269` |
| `monetarie.settlement.fees.calculated` / `.fee_report.generated` | Fees | `fees.ex:64,195` |
| `monetarie.settlement.qrcode.generated` / `.qrcode.paid` | QRCodes | `qr_codes.ex:82-195,439` |
| `monetarie.settlement.reconciliation.completed` / `.discrepancy.resolved` | Reconciliation | `reconciliation.ex:105,260` |
| `monetarie.dict.keys.{created,deleted,blocked,unblocked}` | Keys | `keys.ex:384,453,781,865` |
| `monetarie.dict.claims.*`, `monetarie.dict.infractions.*`, `monetarie.dict.recovery.*` | Claims/Infractions/FundsRecovery | `claims.ex:647-1342`; `infractions.ex:148-1022`; `funds_recovery.ex:133-1459` |
| `monetarie.dict.med.infraction_received` | Infractions (poll inbound) | `infractions.ex:731` |
| `monetarie.dlq.<subject>` e `monetarie.dlq.poison.<consumer>` | BaseWorker / PoisonPolicy | `base_worker.ex:635`; `poison_policy.ex` |

Todos os caminhos de evento de dinheiro usam a outbox `Shared.Nats.Publisher.publish_async/2` (job Oban na mesma transação de banco, `publisher.ex:69-99`); RPC e telemetria usam `Gnat.pub` direto.

## 10. Webhooks

O schema Postgres `monetarie_webhook` existe (`participants` com endpoints payin/payout/refund/infraction, segredo e mTLS; `delivery_log` com request/response/tentativas), mas NÃO há produtor nem worker de webhook no código da cabine (grep de `webhook` só retorna comentários e rotas de outros contextos). O dispatch de webhooks a parceiros é responsabilidade do Core. O evento de negócio equivalente que a cabine emite é `monetarie.settlement.qrcode.paid` (`qr_codes.ex:412-451`, dedup por update de 1 linha).

## 11. Timeline e Monitor

- Não existe tabela `message_timeline` na cabine (essa tabela é do Core). A linha do tempo local é `monetarie_spi.message_history` (transições SYSTEM/USER/BACEN, PK composta, não particionada) e `monetarie_spi.transaction_status_history` (com `trace_id`).
- O Monitor de operações do pix-admin lê `monetarie_audit.xml_audit_logs` e agrupa por `end_to_end_id`, com fallback para `pi_resource_id` (caso camt.060 -> camt.05x): `pix/backend/apps/settlement_service/lib/settlement_service_web/controllers/admin/monitor_controller.ex:33,58-95`. Todo inbound ICOM é espelhado para `xml_audit_logs` pelo `AckTracker.mirror_inbound_to_monitor/1` (`ack_tracker.ex:161-199`).
- WebSocket: socket `/socket` -> `SettlementServiceWeb.MonitoringChannel`, tópicos `transactions:live`, `system:health`, `settlement:status`, `dict:operations`, `bacen:channels`, `queues:depth`, `monitor:operations` (`pix/backend/apps/settlement_service/lib/settlement_service_web/channels/monitoring_channel.ex:4-27`).

## 12. COSIF da cabine

Contabilidade no `settlement_service` (`SettlementService.Accounting`): `chart_of_accounts`, `journal_entries`, `accounting_events`, `cost_centers` (schema `monetarie_settlement`, migration `20260705130000_create_accounting_tables.exs`). O lançamento acontece quando o `SettlementObligationWorker` consome `monetarie.spi.transaction.settled` e chama `Accounting.create_accounting_event` (`settlement_obligation_worker.ex:392-417`); o par débito/crédito por direção usa as contas âncora `1.1.2.10.01.10.001` (disponibilidade) e `4.9.8.10.01.10.002` (obrigações por serviços de pagamento), configuráveis (`settlement_obligation_worker.ex:677-691`). A materialização é fail-fast: evento sem conta COSIF semeada fica em erro com telemetria (`accounting.ex:193-259`). Seed idempotente: `SettlementService.Accounting.CosifSeeder.seed/0` (`cosif_seeder.ex:59-104`).

## 13. Criptografia: HSM (assinatura) vs mTLS (keyfile) vs sidecar

Três planos independentes:

1. **Assinatura XMLDSig (cert CPIA)**: no HSM RTM via `Shared.Crypto.RtmHsm` (endpoint 443, `POST /v1/kmip/{vhsm}/sign-rsa`, contrato RSAHASH com hash SHA-256 cru em hex; cache de sessão 10 min). Config: `RTM_HSM_ENABLED`, `RTM_HSM_BASE_URL` (default `https://monetarie-hsm-hml.priv.rtmcloud.net.br`), `RTM_HSM_VHSM`, `RTM_HSM_CRYPTO_USER`, `RTM_HSM_TOKEN`, `RTM_HSM_PRIVATE_KEY_UID`/`RTM_HSM_PUBLIC_KEY_UID` (`runtime.exs:86-107`). Fallback em software só quando o HSM está desligado.
2. **mTLS de transporte (cert CPIC)**: keyfile em disco montado nas `conn_opts` do pool Finch no boot (nunca por requisição): `Shared.Application.build_bacen_transport_opts/0` com `verify_peer`, truststore ICP-Brasil `pix/backend/apps/shared/priv/certs/icp_brasil_ca_chain.pem`, `partial_chain`, `customize_hostname_check`, `depth: 5` (`application.ex:210-245`). Pools: SPI primário/secundário (6 conexões), DICT (12), ARQ (4) (`application.ex:94-107`). Materialização dos certificados a partir do AWS Secrets Manager no boot: `Shared.Crypto.CertMaterializer` (CPIA para o banco, CPIC para arquivos `chmod 600`, PEERPIA para verificar assinatura inbound; `cert_materializer.ex:31-263`).
3. **Sidecar Go `bacen_mtls_sidecar`**: elimina a chave CPIC do disco, originando o mTLS com assinatura do handshake no HSM (`hsmSigner.Sign`, `main.go:138-146`; TLS fixado em 1.2 porque o HSM só faz PKCS#1 v1.5, `main.go:444-445`). Upstreams default `dict=9101`, `dict_np=9102`, `icom=9103`, `icom_sec=9104`, `arq=9105`, health `:9100` (`main.go:540-543,607-625`), sobrescrevíveis por `SIDECAR_UPSTREAMS`. É ativado apontando `DICT_BASE_URL`/`ICOM_PRIMARY_URL`/`ICOM_SECONDARY_URL`/`ARQ_BASE_URL` para `http://localhost:910x` (`runtime.exs:520-557`; reescrita do download CID em `cid_sync_service.ex:756-770`). A task-def Terraform do `pix-api` não referencia o sidecar (adição operacional por fora do Terraform).

Tabelas: `monetarie_spi_msg.crypto_keys` (tipos CPIC/CPIA/CERTQRC/PEERPIA, `certificate.ex:15-48`) e `monetarie_spi_msg.crypto_private_keys` (chaves cifradas AES-256-GCM quando não estão no HSM). O QR dinâmico/CobV assina payload JWS com o cert CERTQRC e publica o JWK Set em `/qrc/jwks`.

## 14. Endpoints e portas

| Serviço | Porta | Health | Papel |
|---|---|---|---|
| `SettlementServiceWeb.Endpoint` | 4003 (`SETTLEMENT_PORT`) | `GET /health`, `/ready`, `/metrics` (PromEx) | API principal (o ALB do `pix-api` aponta para 4003); gateway que faz proxy interno para DICT e SPI |
| `SpiServiceWeb.Endpoint` | 4002 (`SPI_PORT`) | `GET /health`, `/ready` | API SPI interna |
| `DictServiceWeb.Endpoint` | 4001 (`DICT_PORT`) | `GET /health`, `/ready`, `/metrics` | API DICT interna |

Evidência: `runtime.exs:415-467`; adapter Bandit nos 3 (`config.exs:218,229,240`); Dockerfile expõe 4001/4002/4003 (`Dockerfile:77-81`).

Rotas principais do gateway (`pix/backend/apps/settlement_service/lib/settlement_service_web/router.ex`):

- Públicas: `GET /qr/v2/:access_token` (resolver do QR dinâmico/CobV, payload JWS) e `GET /qrc/jwks` (JWK Set), linhas 53-61.
- `/api/v1/*`: auth (login/MFA/socket-token), payments/transactions (com pipeline idempotente), returns, balance, statements, alcada, echo, recurrences (proxy SPI, linhas 486-584); sessions, qr-codes, fees, reconciliation, files, schedules (settlement); dict/* e `/api/v2/*` (proxy DICT: entries, claims, MED 2.0, fraud markers, infraction reports); admin (monitor, cid sync, api-clients, scheduled-messages, reda, dlq, certificates); reports, messages (Central de Mensagens ISO 20022), accounting, permissions, simulator.
- WebSocket `/socket` (Phoenix Channels, JWT).

Frontend `pix-admin-ui`: nginx :8080, proxy `/api` e `/socket` para o `pix-api` (upstream `PIX_API_UPSTREAM`, default `http://pixapi-h.monetarie.internal:80`), SPA fallback (`pix/frontend/admin/nginx.conf`, `pix/frontend/admin/Dockerfile:22-34`).

## 15. Env vars por função

Fonte principal: `pix/backend/config/runtime.exs` (quase tudo dentro de `if config_env() == :prod`, linha 170). Valores de deploy: `infra/aws/greenfield/ecs.tf` (serviço `pix-api`, linhas 78-145).

| Função | Variável | Default / observação |
|---|---|---|
| BACEN liga/desliga | `BACEN_ENABLED` | `false` (`runtime.exs:346`); `true` no ECS. Se true, exige `INBOUND_SOURCE`, `BACEN_ENV`, `CERT_SECRET_PREFIX` (fail-fast) |
| Ambiente BACEN | `BACEN_ENV` | `homolog` ou `production` (`runtime.exs:498`) |
| Fonte inbound | `INBOUND_SOURCE` | `bacen_mock_nats` sem BACEN; `icom_http` com BACEN (`runtime.exs:348-374`) |
| Identidade | `BACEN_ISPB` (obrigatória, `runtime.exs:622`), `BACEN_PARTICIPANT_NAME` (`MONETARIE`), `BACEN_CNPJ`, `BACEN_COUNTERPARTY_ISPB` (`00038166`) | ISPB Monetarie 46026562 |
| DICT | `DICT_BASE_URL` (prod `https://dict.pi.rsfn.net.br:16422/api/v2`; homolog porta 16522), `DICT_NP_BASE_URL` (16432/16532) | `runtime.exs:516-544`; apontar para `localhost:9101/9102` roteia pelo sidecar |
| ICOM | `ICOM_PRIMARY_URL` (16422/16522), `ICOM_SECONDARY_URL` (17422/17522), `ICOM_MAX_SLOTS` (teto 6; ECS 4) | `runtime.exs:547-575`; `application_supervisor.ex:114-122` |
| Modo DICT | `DICT_EXTERNAL_MODE` | `local` (default), `mirror`, `bacen` (fail-fast em inválido, `runtime.exs:109-124`); `bacen` no ECS |
| DICT extras | `DICT_OWNERSHIP_ENFORCED` (false), `DICT_OWNERSHIP_TTL_S` (3600), `DICT_VERIFY_ENFORCED` (false), `DICT_BUDGET_MODE` (`advisory`), `DICT_BUDGET_CATEGORY` (`H`) | `runtime.exs:131-160` |
| Simulador | `SIMULATOR_ENABLED` | `false` nos ambientes reais (lido em `Shared.Application`; ECS `ecs.tf:97`) |
| CID sync | `CID_FULL_SYNC_ENABLED`, `CID_EVENT_POLL_ENABLED`, `CID_ENTRY_INGEST_ENABLED` (defaults false no código), `CID_FULL_SYNC_INTERVAL` (ECS 21600s), `CID_EVENT_POLL_INTERVAL` (ECS 300s) | `cid_sync_service.ex:71-88`; ECS `ecs.tf:114-118` |
| HSM | `RTM_HSM_ENABLED` (false), `RTM_HSM_BASE_URL`, `RTM_HSM_VHSM` (ECS 60042), `RTM_HSM_CRYPTO_USER`/`RTM_HSM_TOKEN` (secrets), `RTM_HSM_PRIVATE_KEY_UID`/`RTM_HSM_PUBLIC_KEY_UID`, `RTM_HSM_KEY_SIZE_BITS` (2048), `RTM_HSM_TIMEOUT_MS` (8000) | `runtime.exs:86-107` |
| mTLS/certs | `CERT_SECRET_PREFIX` (`monetarie/{env}/pix/cert`), `BACEN_CA_CERT_PATH`, `BACEN_CLIENT_CERT_PATH`, `BACEN_CLIENT_KEY_PATH`, `BACEN_CLIENT_KEY_PASSWORD`, `BACEN_TLS_SERVER_NAME`, `CERT_MATERIALIZER_ENABLED` (true), `SECRETS_MANAGER_PUSH_ENABLED` (true), `AWS_REGION` (`sa-east-1`) | `runtime.exs:579-619`; não existe `BACEN_CERT_PEM_FILE` na cabine (isso é do SPB) |
| QR/CobV | `QR_BASE_URL` e `QRC_JKU` | `System.fetch_env!` FAIL-FAST no release (`runtime.exs:681-682`); dev `http://localhost:4003/qr/v2` e `/qrc/jwks` (`config.exs:111-112`). Atenção: não constam na task-def Terraform do pix-api, precisam ser injetadas no deploy |
| NATS | `NATS_HOST` (`localhost`; ECS `nats-h.monetarie.internal`), `NATS_PORT` (4222), `NATS_USER`/`NATS_PASS`, `NATS_ENABLED` (workers), `NATS_STREAM_REPLICAS`, `NATS_STREAM_MAX_BYTES` | `runtime.exs:324-339`; `jetstream.ex:512-519` |
| Banco | `DATABASE_URL` (secret `monetarie/{env}/db/mon_pix/url`) ou `DB_HOST/PORT/USER/PASS/NAME`; `DATABASE_SSL*`; pools por repo `PIX_SHARED_POOL_SIZE` (8), `PIX_DICT_POOL_SIZE` (6), `PIX_SPI_POOL_SIZE` (8), `PIX_SETTLEMENT_POOL_SIZE` (6) | `runtime.exs:172-321` |
| Portas HTTP | `DICT_PORT` (4001), `SPI_PORT` (4002), `SETTLEMENT_PORT` (4003), `SETTLEMENT_CHECK_ORIGIN` | `runtime.exs:415-456` |
| Flags de negócio | `TWO_PHASE_PIX_IN` (false, PROIBIDA ligar), `PIX_IN_ORPHAN_AUTO_REPUBLISH` (false), `PIX_IN_ORPHAN_LOOKBACK_HOURS` (24), `PIX_XSD_GATE_ENFORCE` (true), `ALCADA_PIPELINE_ENABLED` (false), `SPI_VERSION_OVERRIDES`, `ECHO_PROBE_ENABLED` (false) | `runtime.exs:24-84`; `echo_probe_worker.ex:221-232` |
| Drenagem | `DRAIN_LAMEDUCK_MS` (5000), `DRAIN_SHUTDOWN_TIMEOUT_MS` (90000) | `runtime.exs:410-411` (exige `stopTimeout` >= 100s na task ECS) |
| Auth/segredos | `SECRET_KEY_BASE`, `JWT_SECRET`/`GUARDIAN_SECRET_KEY`, `RSA_PRIVATE_KEY`/`RSA_PUBLIC_KEY`, `MONETARIE_PIX_ADMIN_PASSWORD` (todos via Secrets Manager) | `runtime.exs:381-396`; `ecs.tf:134-144` |
| Redis | `REDIS_HOST`, `REDIS_PORT` (6379): dedup inbound, idempotência, blacklist de token | `ecs.tf:94-95` |
| Sidecar (Go) | `SIDECAR_UPSTREAMS`, `PIC_CERT_PEM`/`PIC_CERT_FILE`, `ICP_CA_FILE`, `RTM_HSM_*`, `SIDECAR_TLS_SESSION_CACHE`, `SIDECAR_SELFTEST` | `pix/services/bacen_mtls_sidecar/main.go:409-559` |

Não existem `BACEN_SPI_BASE_URL` nem uso de TigerBeetle (`TB_*`) na cabine PIX (o ledger TigerBeetle é do Core).

## 16. Nomenclatura ECS e deploy

- **`pix-api`**: imagem ECR `monetarie/pix-api`, container port 4003, Fargate ARM64, cpu 512 / mem 1024, target group `mon-pix-api-h`, health `/health`, hosts `pixapi-h.monetarie.internal` e `pix-api-h.monetarie.internal` (em produção sem sufixo `-h`): `infra/aws/greenfield/ecs.tf:78-145`, `infra/aws/greenfield/alb-internal.tf:13-22`. Env no ECS de homolog: `BACEN_ENABLED=true`, `BACEN_ENV`, `DICT_EXTERNAL_MODE=bacen`, `INBOUND_SOURCE=icom_http`, `CID_*=true`, `RTM_HSM_*`, `ICOM_MAX_SLOTS=4`, `SIMULATOR_ENABLED=false`.
- **`pix-admin-ui`**: imagem `monetarie/pix-admin-ui`, nginx :8080, target group `mon-pix-admin-h`, host `pixadmin-h.monetarie.internal` (`ecs.tf:304-311`; `alb-internal.tf:84-90`).
- Backend buildado com `hexpm/elixir:1.17.3-erlang-27.3.4-debian-bookworm-slim`, release `monetarie_pix`, runtime `debian:bookworm-slim` com `libxml2-utils` (xmllint), wkhtmltopdf e poppler-utils, usuário não-root (`pix/backend/Dockerfile:4-84`).
- O simulator-frontend e o sidecar mTLS não fazem parte da task-def Terraform (o sidecar foi adicionado operacionalmente em produção por edição de task-def, fora do TF).

## 17. Pontos de atenção encontrados nesta leitura

1. `QR_BASE_URL` e `QRC_JKU` são fail-fast no boot do release (`runtime.exs:681-682`) e não constam na task-def Terraform do `pix-api`: qualquer nova task-def precisa injetá-las manualmente ou o settlement_service não sobe.
2. `TWO_PHASE_PIX_IN` segue existindo no código (`runtime.exs:24`, `inbound_processor.ex:2858`): PROIBIDO ligar em ambiente real (o gatilho settled só existe no simulador; ligar significa cliente nunca creditado).
3. O enum de reject codes tem **43** códigos (não 44 como registrado em documentos anteriores): `reject_codes.ex:26-32`.
4. `monetarie_spi.messages` é particionada por `operation_time` (não por `created_at`): `mon_pix_schema_clean.sql:5361`; filhos mensais mantidos pelo `Shared.PartitionManager` junto com `monetarie_auth.activity_log` e `login_history`.
5. As tabelas `message_timeline`, `fee_transactions` e `fee_split_transactions` citadas em handoffs pertencem ao Core; na cabine a timeline é `message_history`/`xml_audit_logs`.
6. O schema `monetarie_webhook` está órfão de código na cabine (dispatch de webhook é do Core).
7. Três Ecto schemas sem `@schema_prefix` (`dict_keys`, `participants` em `shared/schemas/participant.ex`, `settlement_batches`) apontam para tabelas inexistentes em `public`: código aparentemente morto.
