# Mapa funcional — Cabine PIX Monetarie (estado atual)

Baseline para a matriz de paridade contra o legado. Fonte: `/Users/luizpenha/monetarie/pix/` (read-only). Data do levantamento: 2026-07-23.

## Visão geral da arquitetura

Umbrella Elixir 1.17 / Phoenix, 4 apps. Vue 3 no admin. PostgreSQL (Aurora, banco `mon_pix`, 12 schemas / 207 tabelas), NATS JetStream, Redis, TigerBeetle (via Core), HSM RTM (assinatura), IBM MQ (só SPB, outro repo).

| App | Porta | Papel |
|-----|-------|-------|
| `shared` | - | Schemas Ecto, cliente/cripto BACEN, ICOM token budget, outbox, auth, NATS, Redis, simulador |
| `dict_service` | 4001 | Diretório de chaves PIX (DICT API v2.10.0/2.12.1), claims, MED, CID |
| `spi_service` | 4002 | Liquidação instantânea (SPI), ICOM long-poll, MED, recorrências, saldos, extratos |
| `settlement_service` | 4003 | **API gateway** (único exposto ao ALB) + netting + reconciliação + contabilidade + QR + fees + APIX + CoreEventProcessor |

Fluxo money-path canônico: **Core → NATS (`monetarie.core.pix.*`) → CoreEventProcessor (settlement) → `monetarie.spi.outbound.send` → OutboundSender (spi) → HSM (assina) → BACEN/ICOM → resposta inbound → InboundProcessor/StatusUpdater → `monetarie.spi.transaction.settled` → SettlementObligationWorker → credita Core**.

Streams NATS: `MONETARIE_SPI`, `MONETARIE_DICT`, `MONETARIE_SETTLEMENT`, `MONETARIE_CORE`, `MONETARIE_AUDIT`, `MONETARIE_DLQ`. Padrão outbox (Oban PG-backed) obrigatório no money-path (`Shared.Nats.Publisher.publish_async` dentro de `Repo.transaction`).

---

## 1. SPI / Liquidação (spi_service + settlement_service)

### Fluxos money-path

**PIX OUT (envio):**
1. Core publica `monetarie.core.pix.payment_request` → `CoreEventProcessor.handle_payment_request` (settlement).
2. Resolve ISPB remetente fail-closed (`resolve_sender_ispb`; erro → `SENDER_ISPB_UNRESOLVABLE`).
3. Resolve E2E: por CHAVE consome **obrigatoriamente** o E2E da consulta DICT cacheada (`Shared.E2eCache`, TTL); expirado → `DICT_CONSULT_REQUIRED`. MANU (agência+conta) gera E2E novo de 32 chars.
4. **Debit-then-send**: bloqueia saldo na Conta PI (`check_and_block_balance`) ANTES de enviar; sem saldo → `INSUFFICIENT_PI_BALANCE`.
5. Insere linha `monetarie_spi.messages` (PDNG) + queima E2E (`Shared.E2eBurn`, barreira estrutural anti-duplicata) na MESMA `SettlementService.Repo.transaction`; monta pacs.008 (dados do recebedor da consulta DICT cacheada; pagador do Core; `require_debtor_document!` fail-fast); valida semântica (`Pacs008SendValidator`); publica `monetarie.spi.outbound.send`.
6. `OutboundSender` (spi): gate pré-envio → assina XMLDSig (HSM, 3 References) → gate XSD oficial pós-assinatura (fail-closed) → claim de envio (`OutboundSendClaim`, anti re-POST) → débito de token ICOM (`IcomTokenBudget`, custo por operação manual §2.2.1.5) → POST ICOM primário (CPM). ACK HTTP NÃO liquida; só marca ACSP e confirma bloqueio.
7. `StatusUpdater` consome `monetarie.spi.transaction.*` e processa pacs.002 (ACSP/ACCC/ACSC/STLD/RJCT) com CAS de estados terminais; ACSC/STLD → `settled` → publica `monetarie.spi.transaction.settled`.
8. `SettlementObligationWorker` (settlement): grava obrigação de netting (idempotente por E2E) + instrui crédito no Core (`monetarie.settlement.transaction.credited`) + gera AccountingEvent/JournalEntry COSIF.

**PIX IN (recebimento):** `InboundProcessor` (spi) consome `monetarie.spi.inbound.received` (pacs.008 chega já liquidada pelo SPI). Valida XSD/assinatura/sanções, valida conta no Core via RPC NATS (`pix.core.validate_account.request`, subject fora de qualquer capture — fix do incidente 13/07), responde pacs.002 (`send_pacs002_response`), publica `transaction.received/settled`. Rejeição → pacs.002 RJCT com `RejectCodes` (fonte única, 44 códigos). Trata camt.014, camt.054, admi.002/004, pain.014, reda.016/041.

**Devolução (pacs.004):** trilho ÚNICO pelo `ReturnProcessor` (spi, consome `monetarie.spi.return.>`). Origem cliente: `CoreEventProcessor.handle_return_request` deriva a VERDADE da pacs.008 inbound original (valor/data/contrapartes, nunca do payload do chamador) e publica `RETURN_CREATED`. `ReturnProcessor` valida elegibilidade (janela 90d), teto cumulativo (`CumulativeCap`, advisory lock), gera RtrId (`return_id_registry` burn estrutural), monta pacs.004 → outbound. Desfechos `return.settled`/`return.rejected` ao Core (libera hold). 4xx do BACEN gated por `PIX_RETURN_4XX_TERMINAL` (regulatório).

**MED (spi_service/med + dict funds_recovery):** Res. BCB 493/2025. `SpiService.Med` — claims de fraude, bloqueio cautelar 72h, chain blocking até 5 hops, bloqueio parcial, janela 90 dias. Resolução camt.029 (`ResolutionConsumer`, libera cautelar), cancelamento camt.055 (`CancellationConsumer`). Infração/recuperação NÃO são mensagens SPI — vão pela API REST do DICT (ver domínio 3).

### Workers (SpiService.Workers.Supervisor, one_for_one, só com NATS)
`InboundProcessor`, `OutboundSender`, `ReturnProcessor`, `StatusUpdater`, `PaymentStatusReconciler` (pacs.008 sem pacs.002 → camt.060), `StuckOutboundChecker` (quarentena), `HsmKeepWarm` (GET /health 20s, evita handshake TLS frio), `Med.InfractionResponder/ResolutionConsumer/CancellationConsumer`, `Recurrences.Scheduler/Execution/Notification`. Também: `PixInOrphanReconciliation`, `RemunerationSweepWorker`, `EodSnapshotWorker`, `OutboundBatch` (empacotamento de até 10 pacs.008/envelope, **flag OFF**).

### Settlement workers (SettlementService.Workers.Supervisor)
Money-path (todos os pods, HA): `SettlementObligationWorker`, `CoreEventProcessor`. Singletons (leader-gate Redis `ClusterSingleton`): `Scheduler` (janelas de netting), `CycleScheduler` (timer intradia), `FileImporter`.

### ICOM (transporte BACEN — spi_service/icom)
Long-poll HTTP por BCB Manual das Interfaces v1.12 §4. `ApplicationSupervisor` liga CPM/CSM só com `INBOUND_SOURCE=icom_http`. `Cpm/Csm.Coordinator` = leader eleito por advisory lock PostgreSQL (singleton cluster-wide); pool de até **6 slots/canal** (`ICOM_*_MAX_SLOTS`, teto 6). `NatsBridge` publica mensagens BACEN recebidas; `PendingRepublisher` varre persistidas-mas-não-entregues; `AckTracker`, `HealthMonitor`, `Heartbeat` (ETS estável). Cap global 6+6 ao BACEN via `Shared.Bacen.ConnSemaphore` (Redis). Financeiro → CPM; não-financeiro → CSM (`ChannelRouter`).

### Integração BACEN (shared/bacen)
`Client` (mTLS ICP-Brasil verify_peer, sem proxy), `ChannelRouter`, `IcomTokenBudget`, `ConnSemaphore`, `SpiClient`, `DictClient`. ISO 20022 (`Iso20022.MessageBuilder/MessageParser/RejectCodes/SpiVersion/TextSanitizer`): pacs.008/002/004, camt.060/054/053/052, pain.009/011/012/014, reda.016/041, pibr.001/002 (echo), admi.002/004. Cripto: `XmlSigner` (RSA-SHA256, 3 refs SPI / 2 refs DICT), `XmlVerifier`, `ExcC14n`, `RtmHsm`, `CertificatePool`, `JwsSigner`, `JwkSet` (QR dinâmico). Gate XSD oficial version-aware (`SpiXsd`, xmllint). Assinatura/decifra vivem SÓ na cabine (Core nunca assina).

### Conciliação
`SettlementService.Reconciliation` (matching cabine×BACEN, discrepâncias), `SpiService.DailyReconciliation` (+ balance_snapshots), `PixInOrphanReconciliation`, `PaymentStatusReconciler`, `OperationQuery` (camt.060 por E2E). Monitoring: `AnsMonitor` (1600ms), `IcomLagMonitor`, `DlqDepthMonitor`, `ConsumerLivenessMonitor`, `PixInOkRateMonitor`, `EchoProbeWorker`, `AlertEngine` (operational_alerts persistidos).

---

## 2. DICT (dict_service)

- **Consulta de chave**: `Keys.get_entry`, `EntryController` (`GET /api/v2/entries/:key`, `/entries/by-account`), batch (`POST /keys/check`). Limites 5 PF / 20 PJ. Rate limit por tier BACEN (`DictRateLimit`).
- **Cadastro/exclusão**: `create_entry` (valida tipo/owner/conta/ISPB/limite/claim pendente/ownership), `delete_entry` (+ variante com motivo no path `DELETE /entries/:key/:reason`), block/unblock.
- **Claims (portabilidade/posse)**: `Claims` — OPEN→WAITING_RESOLUTION→CONFIRMED/CANCELLED/COMPLETED, prazos D+7/D+14/D+30, ownership validation (`Ownership`). `ClaimDeadlineConsumer` (timers durável via `Nats-Msg-Deliver-After`).
- **Infrações**: `Infractions` (DICT 2.11/2.12.1). Status OPEN→ACKNOWLEDGED→CLOSED/CANCELLED. `POST /infraction-reports/` do BACEN está morto (410 Gone) — a notificação nasce DENTRO de uma Recuperação de Valores.
- **MED 2.0 / Funds Recovery**: `FundsRecovery` — create/update/cancel/start-refund, tracking graph (multi-hop), refund requests, `MedContact` (contato institucional obrigatório em modo bacen). `InfractionResponseConsumer`.
- **CID/eventos**: `Sync.CidSyncService` (singleton, full 6h + eventos 5min), `CidEntryIngestor`, `CidController` (files/events).
- **Cache E2E**: `Shared.E2eCache` (Redis, TTL 5min, correlação DICT→SPI). **Reservas**: `Shared.Dict.E2eReservation` (espelho durável PG do cache, fallback a restart do Redis). `Shared.E2eBurn`, `Shared.ReturnIdBurn` (identidade forte anti-replay).
- **Fraud markers**: `FraudMarkers` (`fraud_markers`), `FraudMarkerEnumMapper`.
- **Adapters**: `BacenAdapter` com `Real`/`Local`/`Simulator` (`DICT_EXTERNAL_MODE`).
- **Reconciliação DICT**: `DictExternalReconciler`, `DictInboundPollWorker`.

---

## 3. MED / Infrações / Devoluções — onde vivem

| Peça | App / módulo | Endpoints | Status |
|------|--------------|-----------|--------|
| Devolução pacs.004 | spi `ReturnProcessor` | `POST /transactions/:id/return`, `/returns` | Funcional (trilho único) |
| Claims fraude / cautelar / chain block | spi `Med` | interno (NATS) | Funcional |
| Infraction reports | dict `Infractions` | `/api/v2/infraction-reports` | Funcional (via recovery; endpoint direto BACEN 410) |
| Funds recovery (MED 2.0) | dict `FundsRecovery` | `/api/v2/funds-recoveries`, `/tracking-graph`, `/refund` | Funcional |
| Fraud markers | dict `FraudMarkers` | `/api/v2/fraud-markers` | Funcional |
| Refund requests | dict `FundsRecovery.RefundRequest` | `/api/v2/refund-requests` | Funcional |

Telas admin: `med/{MedHub,Infraction*,FundsRecovery*,FraudMarker*,Refund*}`. Compliance report agrega infrações reais.

---

## 4. Multiliquidação / Lote — O QUE EXISTE E O QUE NÃO EXISTE

**LACUNA PRINCIPAL.** Não há multiliquidação de negócio (importação de arquivo de ordens, agendamento em lote, bloqueio, validação de crédito por linha, QR dinâmico em lote) na cabine.

- ❌ **Batch de pagamentos**: `POST /api/v1/batch` (+ list/get) do gateway proxia para `/api/v1/batch` do SPI que **NÃO EXISTE** (rota removida — "pain.001/002 fictícias, não em XSD raw v5.12"). Resulta em 404. Tabelas `monetarie_spi.batches`/`batch_items` existem no dump legado mas **nenhum código as popula**.
- ❌ **Importação de arquivo de ordens**: `FileController.import`/`export`/`list_imports` são **STUBS** (retornam `"accepted"`/lista vazia, não processam nada). Tabelas `payment_files`/`file_imports`/`file_import_records` existem, sem uso real de ingestão de lote.
- ❌ **QR composto / split payment (multi-recebedor)**: `CompositeQRCodeController` → **501 Not Implemented** (stub explícito). Tabelas `qr_code_receivers` existem.
- ❌ **PIX agendado (data futura, avulso)**: não vive na cabine — é o `ScheduledPix` do Core.
- ⚠️ **`OutboundBatch`**: existe, mas é empacotamento TÉCNICO de até 10 pacs.008 por envelope ICOM (throughput / amortiza assinatura+POST), **flag OFF**, sem semântica de negócio de lote.
- ✅ **Recorrência (PIX Automático)**: `SpiService.Recurrences` — pain.009/011/012, Res. BCB 505/2025, lifecycle completo com scheduler que gera pacs.008 periódicos. É a única forma de pagamento programado na cabine.
- ✅ **FileImporter** (settlement): existe mas só para arquivos BACEN INBOUND (camt.052/053/054, rco) — não é importação de ordens de pagamento.
- ✅ **APIX001** (arquivo regulatório mensal): `SettlementService.Apix` — geração DEPRECADA (o oficial é o `Cadoc1201` do Core).

---

## 5. Schema Ecto (principais tabelas por schema — 207 no total)

- **monetarie_spi** (mensagens/pagamentos/saldos/filas): `messages` (+ partições `messages_2026_*`, **produção canônica**), `payments`, `transactions` (tabela paralela quase vazia), `returns`/`transaction_returns`/`return_id_registry`, `balances`/`balance_blocks`/`balance_history`/`balance_cache`, `statements`/`statement_entries`/`statement_exports`, `bacen_inbound`/`bacen_outbound`, `participants`, `recurrences`/`recurrence_instructions`, `alcada_parameters`/`alcada_registrations`/`alcada_vistos`/`alcada_held_messages`, `icom_sessions`/`icom_received`, `camt060_requests`, `echo_probes`, `operational_alerts`, `pix_in_orphans`, `spi_remuneration_sweeps`, `e2e_id_seeds`/`endtoend_id_registry`/`e2e_outbound_burns`, `outbound_send_claims`, `daily_reconciliations`/`daily_balance_snapshots`, `batches`/`batch_items` (mortas).
- **monetarie_dict**: `keys`, `claims`, `funds_recoveries`, `infraction_reports`/`infractions`, `fraud_markers` (via migration), `tracking_graphs`+contas/pessoas/transações, `cid_files`/`cid_events`, `institutions`, `ownership_validations`, `refund_requests`, `dict_external_operations`, `e2e_reservations`, `participant_buckets`/`rate_limit_policies`.
- **monetarie_settlement**: `netting_cycles`/`netting_operations`/`netting_positions`/`netting_transactions`, `settlement_sessions`, `chart_of_accounts`/`journal_entries`/`accounting_events`/`cost_centers`, `qr_codes`/`qr_code_receivers`/`emv_payments`, `fee_tables`/`fee_rules`/`fee_entries`/`fee_exemptions`, `apix001_files` (migration), `system_configs`, `indirect_participants`, `holidays`, `payment_files`/`file_imports` (stub).
- **monetarie_spi_msg**: `crypto_keys`, `crypto_private_keys`, `xml_messages`.
- **monetarie_spi_ref**: `banks`, `status_codes`, `message_types`, `message_versions`, `error_codes`, `domain_values`, `xsd_schemas`, `bacen_pix_participants`.
- **monetarie_audit**: `xml_audit_logs`, `bacen_api_validations`, `audit_daily_summary`, `retention_jobs`.
- **monetarie_auth**: `users`, `groups`/`group_features`/`features`, `user_groups`, `institutions`/`entities`, `audit_logs`, `activity_log` (partições), `login_history` (partições), `mfa_configurations`/`mfa_events`, `api_clients` (migration), `scheduled_message_jobs` (migration).
- **bacen_simulator**: 25 tabelas (config, scenarios, exchanges, dict_entries, med, cid, balances).

Migrations: `shared` = 59 (dono do schema; base = SQL dump `mon_pix_schema_clean.sql`, 207 tabelas), `settlement_service` = 15 (QR, APIX, netting, oban), `spi_service`/`dict_service` = 0. `monetarie_spi.messages` status: 1=PDNG 2=ACSP 3=ACCC 4=ACSC 5=ACTC 6=ACWC 7=STLD 8=RJCT 9=CANC 10=RTRN.

---

## 6. Alçadas / Autenticação / Parametrização

- **Alçada (4 olhos)**: `SpiService.Alcadas` (`alcada_parameters`/`registrations`/`vistos`) — por valor, janela horária, tipo de mensagem, acumulado diário. Endpoints `/alcada/*` (parâmetros, check, registrations, visto). `Shared.Alcada.SendGate` + `HeldMessages` gate no ENVIO MANUAL (tela Construir Mensagem); **flag `ALCADA_PIPELINE_ENABLED` default OFF**; caminho automático do Core é ISENTO por construção (já passa por limites no Core, ANS 1,6s). Telas `alcada/{Parameters,Pending,History}`.
- **Autenticação admin**: JWT em cookie HttpOnly (`pix_session`) + CSRF double-submit + login RSA-OAEP (`/auth/public-key`) + MFA TOTP/backup (`Shared.Auth.MFA`). Cross-service: cadeia `JWT_SECRET`→`GUARDIAN_SECRET_KEY`→`SECRET_KEY_BASE`; tokens Guardian do Core (`iss: monetarie`) aceitos. Plugs `SsoAuth`/`JwtAuth`/`SsoOrJwtAuth`/`Authenticate`.
- **RBAC**: `Shared.Plugs.RequirePermission` (ETS 5min, `group_features`, fail-closed, 403 RFC 7807). Telas `security/{UserList,UserForm,GroupList,Scopes,ApiClients}`, `permissions/*`.
- **Parametrização**: `SystemConfig` (`/system/config`, `/config/parameters`, legacy-parameters), `SecuritySettings` (password/session/login/mfa/ip-restrictions/audit policy), `BalanceParameters`, `automation/ScheduledMessages`, limites por participante (`participants/ApprovalLimitsView`, `LimitsSection`).
- **API clients** (m2m interno): `admin/api-clients` (create/rotate-secret/revoke).

---

## 7. Frontend admin (Vue 3) — telas existentes

Dashboard, Login/MFA. **Transações**: List/Detail/Create, Returns. **Chaves DICT**: KeyList/Create/Detail, DICT Indicators/Policies/Reports/Statistics. **Claims**: List/Detail. **MED**: MedHub, Infraction List/Detail, FundsRecovery List/Detail, FraudMarker List/Detail, Refund. **QR**: QrCodeList/Create. **Saldos**: BalanceDashboard/History/Parameters, CreditRequest/DebitRequest/LiquidityInjection, BalanceRequests. **Contabilidade**: AccountingDashboard/Events/Monitor, ChartOfAccounts, CostCenters, FeeManagement, **NettingCycles**, Reconciliation, SettlementReport, StandardHistory. **Alçada**: Parameters/Pending/History. **Mensagens ISO**: MessageBuilder, MessageCenter, Detail, History. **CAMT.060**: Camt060Tool. **Participantes**: Participants, Operations, ApprovalLimits, LegacyParameters, Webhooks, Responsibles, PixSaquePixTroco, Rco/ResponseTime. **Monitores**: Operations, RealTime, File/Integration/Tariff/IntegrationMonitor. **Relatórios**: Audit/Balance/Compliance/Custom/Fee/Transaction/ReportScheduler/History. **Segurança**: Users, Groups, Scopes, Mfa, ApiClients, Certificates, ChangePassword, SecuritySettings. **REDA**: List/Detail (participantes indiretos). **Sessions**, **CidSync**, **SystemHealth**, **Simulador** (Dashboard/Scenario/Exchange/CapacityTest — app `simulator-frontend` separado), **Statements**, **Recurrences**, **XmlViewer**.

Contabilização COSIF é aba de 1ª classe (chart of accounts, journal entries, accounting events, BCB Circular 4010, balanço/DRE).

---

## 8. Stubs / incompletos declarados

| Item | Local | Estado |
|------|-------|--------|
| Batch de pagamento (`/api/v1/batch`) | gateway→SPI | 404 (rota SPI removida; tabelas mortas) |
| Importação/exportação de arquivo | `FileController` | STUB (retorna "accepted", não processa) |
| QR composto / split payment | `CompositeQRCodeController` | 501 Not Implemented |
| APIX001 geração | `SettlementService.Apix` | DEPRECADO (oficial = Cadoc1201 do Core) |
| RCO monitor | `ConfigController.rco_operations` | Estado honesto/vazio |
| `OutboundBatch` (envelope multi-tx) | spi | Implementado mas flag OFF |
| Alçada no pipeline de envio | `Shared.Alcada.SendGate` | Implementado mas flag OFF (só envio manual) |
| `EventPublisher` (spi) / `SettlementService.NATS.Publisher` | infra | DEPRECATED (0 callsites de produção) |
| `pacs.028`, `pain.001/002`, `admi.005-011`, claim CONTEST, trck.002 | vários | Removidos como "ficção de catálogo" (não em XSD raw v5.12) |
| Tabela `transactions` (monetarie_spi) | schema | Paralela, quase vazia — canônico é `messages` |

---

## Lacunas aparentes vs banco PIX completo (síntese para a matriz)

1. **Multiliquidação / lote de negócio AUSENTE**: sem importação de arquivo de ordens com validação por linha, sem agendamento de lote, sem bloqueio/validação de crédito em massa, sem QR dinâmico em lote. Só recorrência (PIX Automático) cobre pagamento programado.
2. **Split payment / QR composto**: stub 501.
3. **PIX agendado avulso**: fora da cabine (Core).
4. **Importação/exportação de arquivos genéricos**: stub.
5. **API externa/partner PIX**: não existe na cabine (vive no Core); o admin da cabine é interno.
6. **Alçada no envio automático**: por design não retém o caminho Core (ANS 1,6s) — controle fica no Core.
