# Trilha 05: Modelo de dados PostgreSQL, particionamento e consistência (AVIV/coreproviders vs Monetarie)

Data: 2026-07-09. Trilha READ-ONLY do mandato de mapeamento exaustivo AVIV vs Monetarie.
Meta do dono: a Monetarie deve aguentar MUITO mais volume (referência: recorde AVIV de 1,53M transações PIX/dia em 08/07/2026, dado do mandato) com latência MENOR.

Método: leitura direta de código e migrations nos dois repositórios. Toda afirmação técnica carrega evidência arquivo:linha. Onde a fonte é documento (CLAUDE.md, db-dictionary, PDF de comparativo), isso é dito explicitamente. Nada de chamada AWS ou runtime nesta trilha.

Convenção de caminhos:
- AVIV = `/Users/luizpenha/coreproviders` (branch aviv-hml, app OTP `:fluxiq`)
- Monetarie Core = `/Users/luizpenha/monetarie/core/backend`
- Monetarie cabine PIX = `/Users/luizpenha/monetarie/pix/backend` (umbrella: shared, spi_service, dict_service, settlement_service)

---

## 1. Como a AVIV faz

### 1.1 Topologia de repos e pools (3 pools no mesmo Aurora + reader)

A AVIV separa o acesso ao PG em três repos Ecto com pools independentes e identidade própria em `pg_stat_activity`:

| Repo | Pool default (prod) | Papel | Evidência |
|---|---|---|---|
| `Fluxiq.BaseRepo` | `POOL_SIZE` default 100, `statement_timeout=30000`, `application_name=fluxiq_main` | writer, money path | `backend/config/runtime.exs:305-324` |
| `Fluxiq.ReadRepo` | `READ_POOL_SIZE` default 50, `work_mem=64MB` só no reader, `application_name=fluxiq_read` | Aurora reader (fallback para o writer quando `READ_REPLICA_URL` não setada) | `backend/config/runtime.exs:326-350` |
| `Fluxiq.BatchRepo` | `BATCH_POOL_SIZE` default 10, `timeout: 15_000`, `queue_target: 10_000`, `application_name=fluxiq_batch` | bulk inserts (BatchWriter, COSIF sync) | `backend/config/runtime.exs:352-368` |

- `ReadRepo` NÃO está em `:ecto_repos` (nunca migra): `backend/config/config.exs:42-43`.
- Roteamento de leitura por domínio via flag `READ_SPLIT_DOMAINS` (vazio = tudo no writer, no-op seguro): `backend/config/runtime.exs:78-99`.
- `Fluxiq.Reads.with_fallback/2` cai do reader para o writer em erro de conexão, com contador Prometheus, porque um reader saturado já despejou 41% da carga de volta no writer de forma invisível (comentário cita episódio real): `backend/lib/fluxiq/reads.ex:12-45`.
- O comentário do reader explica o `work_mem=64MB`: agregações de analytics estouravam o default de 4MB e derramavam para disco; o parâmetro sobe SÓ nas sessões do reader: `backend/config/runtime.exs:339-346`.
- `BaseRepo`/`BatchRepo` são `Ecto.Repo` puros; `BatchRepo` compartilha o mesmo banco com timeout maior "to avoid blocking normal queries during bulk flushes": `backend/lib/fluxiq/base_repo.ex:24-33`.

### 1.2 Esquema central: `transactions` particionada, PK composta, id = transfer TB

`transactions` nasce particionada por RANGE(`started_at`) com PK composta e id binário de 16 bytes (o transfer id de 128 bits do TigerBeetle, 1:1 com o ledger):

- DDL: `CREATE TABLE transactions (... id BYTEA NOT NULL ... PRIMARY KEY (id, started_at), CONSTRAINT transactions_id_16_bytes CHECK (octet_length(id) = 16), CONSTRAINT transactions_amount_positive CHECK (amount > 0)) PARTITION BY RANGE (started_at)`: `backend/priv/repo/migrations/20260301000003_create_transactions.exs:12-51`.
- 24 partições mensais pré-criadas (2026-2027) + partição DEFAULT: mesmo arquivo, linhas 54-70.
- Índices: 16 índices compostos com `started_at` nas dimensões quentes (from/to account, status, kind+status, requestor) e índices UNIQUE parciais `(transaction_id, started_at)`, `(end_to_end_id, started_at)`, `(idempotency_key, started_at)` com `WHERE ... IS NOT NULL`: linhas 72-93.
- Amounts em BIGINT subcentavos (1 subcentavo = R$0,0001; PG e TB na mesma unidade): `backend/priv/repo/migrations/20260327100002_amounts_to_subcentavos.exs:5-15`.
- `payment_transactions` NÃO existe; tudo vive em `transactions` (doc interno): `docs/coreproviders/db-dictionary.md:287-291`.
- Propriedade chave: `transactions` é INSERT-ONLY em estado final. O trigger de UPDATE foi dropado porque "Transactions no longer undergo status updates — they are inserted in final state (TB-first)": `backend/priv/repo/migrations/20260301000022_drop_daily_summary_update_trigger.exs:5-8`. Isso elimina churn de UPDATE (bloat, HOT chains) na tabela mais quente do sistema.

Satélites:

- `inflow_requests` e `outflow_requests`: particionadas igual (BIGSERIAL + started_at na PK): `20260301000003:123-193`.
- `outbound_requests` (PIX-OUT em voo): NÃO particionada de propósito; é working set pequeno (só in-flight), com `stage SMALLINT` e índice parcial `WHERE stage < 3`, UNIQUE parcial em `idempotency_key` e `end_to_end_id`: `20260301000003:195-243`. O `BalanceGuard` documenta: "outbound_requests: small table (only in-flight, not historical)": `backend/lib/fluxiq/use_cases/payments/pix_out/balance_guard.ex:28`.
- `pending_transactions`: tabela não particionada para retry/estado intermediário: `20260301000003:97-121`.
- `fee_transactions` e `fee_split_transactions`: particionadas RANGE mensal por `charged_at`, PK `(id, charged_at)` (doc interno): `docs/coreproviders/db-dictionary.md:739-740` e migration `20260325200002_create_fee_split_transactions.exs` (grep de PARTITION confirma).
- `message_timeline` (arquivo bidirecional das mensagens OnZ): particionada RANGE(`received_at`), SEM primary key nem surrogate id (dedup vive upstream em `processed_messages`), payload BYTEA com `ALTER COLUMN payload SET STORAGE EXTERNAL` ANTES da primeira partição para herdar out-of-line storage e evitar dupla compressão TOAST (payload já vem `:ezstd.compress(:erlang.term_to_binary(term), 3)`), índices parciais `(key, received_at) WHERE key IS NOT NULL`: `backend/priv/repo/migrations/20260423130000_create_message_timeline.exs:7-60`. Deny-list por tipo via env (`MESSAGE_TIMELINE_TYPES_DISABLED`, default exclui `dict.outbound`, `mgmt.outbound`, `auth.outbound`, os baldes de alto volume sem valor de rastreio): `backend/config/runtime.exs:143-152`.
- `audit_logs`: cutover para particionada RANGE(`inserted_at`) com PK `(id, inserted_at)` via tabela-sombra vazia (índices no pai instantâneos e auto-herdados), partições cobrindo TODO o histórico + default, covering indexes `(resource_id, inserted_at DESC) INCLUDE (...)`, RLS `entity_isolation` recriada verbatim e autovacuum override aplicado por leaf: `backend/priv/repo/migrations/20260621000000_partition_audit_logs_shadow.exs:1-113`.
- `onz_lp_inbox` (inbox durável do long-poll OnZ): tabela ÚNICA, não particionada, DE PROPÓSITO: o dedup exige UNIQUE global em `message_id`, e o PG não impõe UNIQUE global em tabela particionada sem incluir a partition key: `backend/priv/repo/migrations/20260521000001_create_onz_lp_inbox.exs:13-20`. Índices criados com `CREATE INDEX CONCURRENTLY` + `@disable_ddl_transaction`/`@disable_migration_lock` porque é money path (idiom online-migration documentado no próprio arquivo, linhas 22-36).
- `account_balance_checkpoints`: 1 linha por conta, PK `account_id`, colunas `through_at`, `cutoff_at`, `credits/debits/fees/split_credits` BIGINT: `backend/priv/repo/migrations/20260630210000_create_account_balance_checkpoints.exs:12-28`.
- RLS multi-tenant: GUC `app.current_entity_id` inicializado em toda conexão do pool (`RlsInit.init/1`), políticas referenciam `current_setting('app.current_entity_id')`: `backend/lib/fluxiq/infra/repo/rls_init.ex:1-18`.

### 1.3 Estratégia de criação de partições: worker Oban diário, não migration

`Fluxiq.Workers.PartitionMaintenance` (cron `0 1 * * *`: `backend/config/runtime.exs:628`):

- Pré-cria partições mensais 3 meses à frente para 7 tabelas: `transactions inflow_requests outflow_requests fee_transactions fee_split_transactions message_timeline audit_logs`: `backend/lib/fluxiq/workers/partition_maintenance.ex:29`.
- Gate por tabela: só cria se o pai é realmente particionado (`pg_class.relkind = 'p'`), para o worker poder ser deployado antes ou depois de um cutover: linhas 57-75 e 100-109.
- Criação idempotente com DO $$ ... EXCEPTION WHEN invalid_object_definition (42P17 "would overlap") tratado como já-coberto. O comentário registra o incidente: o job morreu diariamente por 14+ dias porque uma partição com outro nome cobria o range, e TTL/purga nunca rodaram: linhas 237-260.
- Pós-criação, verificação de herança de índices (compara contagem partição vs pai e loga warning): linhas 284-313.
- TTL DELETE só para tabelas não particionadas de vida curta: `processed_messages` 7d, `pix_idempotency_keys` 7d: linhas 40-43. `audit_logs` é explicitamente EXCLUÍDA do TTL (trilha regulatória append-only; retenção via DETACH/DROP de partição fria, nunca DELETE de linha; um DELETE cego estava destruindo a trilha além de 90 dias): linhas 31-43.
- Purga batelada do `onz_lp_inbox`: só estado terminal `processed`, mais velho que 30d (default), em lotes de 5.000 por `ctid` com `Process.sleep(200)` entre lotes e cap de 1M por execução; filtro por `received_at` para usar o índice `(state, received_at)` (filtrar por `processed_at` faria Seq Scan de 11M linhas por lote). Contexto: crescimento unbounded chegou a 20GB / 11,5M linhas no deep audit 2026-07-05: linhas 116-204.

Autovacuum por tabela (o PG não aceita storage params no pai particionado, então aplica por leaf via loop em `pg_inherits`): `webhook_deliveries` scale 0.02/0.01 + thresholds fixos, leafs de `transactions` 0.05/0.02, `audit_logs` 0.1/0.05, `qrcodes` 0.05/0.02: `backend/priv/repo/migrations/20260421160001_autovacuum_per_table_overrides.exs:7-61`. Observação: o comentário na linha 17 diz que partições futuras seriam cobertas "via PartitionMaintenance worker", mas o worker NÃO aplica esses storage params nas partições novas que cria (nenhum ALTER TABLE SET em `partition_maintenance.ex`). Gap latente na própria AVIV.

### 1.4 Hot path PIX-in em PG: quantas escritas por transação

O PIX-in da AVIV é two-phase (ACSP -> ACCC, depósito só em ACCC): `backend/lib/fluxiq/use_cases/pix/tb_first/handler.ex:1-19`. Escritas por transação bem-sucedida, na ordem:

1. Inbox durável: 1 INSERT em `onz_lp_inbox` (`ON CONFLICT (message_id) DO NOTHING`) por mensagem long-poll, ANTES do ack do cursor (design da migration): `20260521000001:13-20`; depois UPDATE de estado ao processar (coluna `state`, `attempts`): mesmo arquivo.
2. Dedup durável: 1 INSERT em `processed_messages` via `MessageDedup.try_claim` (ETS read-through + PG com UNIQUE `(message_id, source)`; PG INSERT primeiro, depois cache ETS, sem TOCTOU): `backend/lib/fluxiq/infra/dedup/message_dedup.ex:1-49`.
3. Fase 1: WAL `pending_pix_in_deposits` com `insert_acsp_sent` (ON CONFLICT DO NOTHING em e2e_id) e o intent pendente vai para o Redis com TTL 600s (`@pending_prefix "pix_in_pending:"`): `handler.ex:331-370` e `handler.ex:39-41`.
4. Fase 2 (ACCC): job Oban `pix_in_pg_write` modo `full_pipeline` é enfileirado ANTES do depósito TB (crash safety; delay 30s + preflight PG por e2e para não competir com o caminho inline): `backend/lib/fluxiq/workers/pix_in_pg_write_job.ex:5-57`. Isso é +1 INSERT em `oban_jobs`.
5. Depósito TB, depois PgWriter: 1 chamada de stored procedure `upsert_pix_in_transaction($1..$16)` que faz MERGE INTO `transactions` com `started_at` no ON para partition pruning; `unique_violation` de drift de data entre partições é capturado e tratado como no-op; COSIF foi REMOVIDO do hot path ("COSIF journal entries are NOT inserted here. They are derived by the periodic COSIF catch-up"): `backend/priv/repo/migrations/20260301000020_simplify_upsert_pix_in_transaction.exs:29-71`; chamada em `backend/lib/fluxiq/use_cases/pix/tb_first/pg_writer.ex:278-279`.
6. DRIFT-01: o `started_at` gravado vem do timestamp de commit do transfer no TigerBeetle (único ancoradouro temporal estável em retry), garantindo que as PKs compostas `(id, started_at)` e `(id, charged_at)` saiam byte-idênticas em qualquer retry: `pg_writer.ex:68-80`.
7. Tarifa: `FeeCharger.record_fee_transaction` (INSERT em `fee_transactions` particionada): `pg_writer.ex:214`.
8. O INSERT em `transactions` dispara o trigger `trg_daily_summary_insert` (ver 1.5): +1 ou +2 MERGEs em `account_daily_summaries`.

Total PG síncrono por PIX-in: ~5-7 statements, todos append-only ou upsert idempotente, todos com pruning por partição, zero UPDATE na tabela quente. Caminho alternativo em lote: `BatchWriter` GenServer bufferiza attrs e flusha a cada 1s ou 1.000 linhas com um único `Fluxiq.BatchRepo.insert_all("transactions", rows)` (pool separado); retry 3x mantendo o buffer; exaustão vai para DLQ NATS-table; `push_sync/1` fura o buffer para caminhos críticos (+1-3ms, elimina janela de perda): `backend/lib/fluxiq/use_cases/payment_transactions/batch_writer.ex:1-168`.

Falha de TB: vai para `failed_transactions` via stored procedure própria (`insert_failed_transaction`): `pg_writer.ex:5-6, 307-315`.

### 1.5 Agregados em tempo real: trigger com sharding de 128 buckets (anti hot-row)

- `account_daily_summaries` tem PK `(account_id, day, type, direction, bucket)`: `backend/priv/repo/migrations/20260301000018_create_infrastructure.exs:150-161`.
- `bucket_for_tx(BYTEA)` = primeiros 4 bytes do id `& 127`, `IMMUTABLE PARALLEL SAFE`: mesmo arquivo, linhas 163-173. Ou seja, N inserts concorrentes da MESMA conta no MESMO dia atualizam até 128 linhas distintas em vez de brigar por 1 linha quente (row lock + bloat).
- Trigger `AFTER INSERT ON transactions FOR EACH ROW EXECUTE FUNCTION update_daily_summary_on_insert()`: `20260301000018:384-388`. Corpo simplificado na migration 021: sem filtro de status ("transactions are only inserted in final state"), MERGE 1 do principal + MERGE 2 da tarifa quando `fee_amount > 0`, com fallback EXCEPTION unique_violation -> UPDATE: `backend/priv/repo/migrations/20260301000021_simplify_daily_summary_insert_trigger.exs:30-102`.
- Compactação: `CompactDailySummaries` roda de madrugada (cron `15 3 * * *`) para reduzir N buckets a 1: `backend/config/runtime.exs:674` (e `config.exs:72-73`).
- Rollups por conta para o dashboard admin: 4 tabelas minúsculas `*_account_daily_rollups` (PKs `(account_id, day, ...)`), populadas por cron a cada 10min + backfill, motivadas por caso real: conta 10202 com 444k tx/90d (19,6% do total) fazia cada card custar 200-360ms em scan de partição e ~10s no load frio: `backend/priv/repo/migrations/20260628230000_create_account_daily_rollups.exs:4-18`.

### 1.6 BalanceGuard e BalanceCheckpoint (a lição mais cara da AVIV)

- `BalanceGuard.verify`: `safe_balance = min(tb_available, pg_net_available)`; existe porque créditos fantasma inflaram o TB em R$2,1M e um bot drenou R$538k da cabine Planner; o `min()` exige concordância dos DOIS sistemas: `backend/lib/fluxiq/use_cases/payments/pix_out/balance_guard.ex:6-24, 46-92`.
- Problema de escala: a soma canônica PG desde o cutoff varria TODAS as partições mensais desde 2026-05-08 a cada autorização PIX-OUT, sob advisory lock por conta, custando 263ms/chamada e ~65% do tempo de execução do writer Aurora: `backend/lib/fluxiq/use_cases/payments/pix_out/balance_checkpoint.ex:5-10`.
- Solução: `account_balance_checkpoints` dobra `[cutoff, through_at)` por conta; o hot path lê a linha + delta `[through_at, agora)`; resultado bit-idêntico à soma completa: `balance_checkpoint.ex:12-27, 60-88`.
- Incidente de fronteira DIÁRIA: com fold diário, o delta recrescia o dia todo; no rush noturno cada autorização re-agregava a fatia inteira do dia de uma conta quente, então custo por chamada crescia com o volume diário E a taxa de chamadas crescia com o tráfego: carga do writer proporcional a volume ao quadrado. O writer Aurora bateu no teto de ACU em 2026-07-08, 18h30-20h30 BRT. Correção: fold passa a rolar POR HORA (`BalanceCheckpointPrefold` cron `5 * * * *` + rebuild integral noturno `37 3 * * *`), delta limitado a ~1h de linhas independentemente da hora do dia: `balance_checkpoint.ex:28-43` e `backend/config/runtime.exs:636-643`.
- Anti-stampede: linha adiantada do boundary pedido é usada como está (rebuild ali deixaria chamadas concorrentes de virada de hora dispararem rescans completos `[cutoff, inf)`): `balance_checkpoint.ex:39-43, 124-135`.
- Tesouraria análoga: `TreasuryCanonFold` (fold + delta <=2h em ~ms, em vez do scan all-history de ~2,5s que era a query #1 do reader): `backend/config/runtime.exs:644-648`.

### 1.7 Auditoria: wrapper de Repo com custo explícito

`Fluxiq.Repo` é um wrapper de auditoria sobre `BaseRepo`: `backend/lib/fluxiq/repo.ex:1-17`.

- Leituras, raw SQL, `transaction` e BULK ops (`insert_all/update_all/delete_all`) delegam direto SEM auditoria: linhas 30-76.
- `insert/update/delete/insert_or_update` (+ bangs) auditam quando há `AuditContext`; se o caller NÃO está em transação, o wrapper ABRE uma transação só para acoplar o INSERT do audit ao write (custo extra de BEGIN/COMMIT por mutação avulsa): linhas 82-112 (insert) e equivalentes.
- Default de auditoria é SÍNCRONA (`:audit_async` default false: `repo.ex:326-329`; env `AUDIT_ASYNC` default "false": `backend/config/runtime.exs:57`). Falha do audit síncrono NÃO derruba o write: rescue -> bufferiza: `repo.ex:335-371`.
- `AuditBuffer`: GenServer com fila, flush 1s em produção, `max_buffer` 5.000, e SPILL PARA DISCO (`/tmp/fluxiq_audit_spill`) quando o PG está fora; `SpillRecoveryWorker` (cron `9-59/15`) reingere: `backend/lib/fluxiq/infra/audit/audit_buffer.ex:10-41` e `backend/config/runtime.exs:683`.
- Escape hatches explícitos `insert_without_audit/...`: `repo.ex:318-320`.
- Custo estrutural: +1 INSERT em `audit_logs` por mutação auditada no mesmo commit (durável, porém dobra o write amplification do caminho auditado). Mitigações da AVIV: bulk sem audit, `BatchRepo` sem audit, `audit_logs` particionada com autovacuum próprio, flag `AUDIT_ASYNC` para tirar do caminho síncrono.
- O CLAUDE.md do backend confirma a intenção: "Fluxiq.BatchRepo — separate pool, no audit (infra internal, TigerBeetle sync)": `backend/CLAUDE.md:218, 224`.

### 1.8 Oban: filas, concorrência, cron e higiene do próprio oban_jobs

- Repo do Oban = `BaseRepo` (oban_jobs mora no writer): `backend/config/runtime.exs:616-617`.
- Plugins: Pruner, Lifeline (rescue 30min) e `Oban.Plugins.Reindexer` diário às 04:00 BRT: o índice GIN `args_index` de `oban_jobs` chegou a 3GB para ~2,4k jobs vivos (bloat de fila com churn alto) e fazia o autovacuum de `oban_jobs` ser o consumidor #1 do writer (deep audit 2026-07-05): `backend/config/runtime.exs:618-625`.
- Cron de produção com ~45 entradas TODAS com offset defasado (`1-59/5`, `3-59/10`, `2-59/15`, `14-59/30`...) para os sweeps não colidirem no mesmo segundo: `backend/config/runtime.exs:627-728`. Exemplos de calibração por incidente: `TbPgDriftMonitor` de */5 para */15 porque re-escaneava constantemente e derramava no writer (contenção com depósito PIX-IN): linhas 651-655; `CosifInternalLegsSync` 1x/dia off-peak "disciplina anti-incidente": linhas 676-681.
- Filas com concorrência via env: `default 10, webhooks 5, pix_in_pg_write 10, pix_out_retry 10, onz_lp_process 6` (6 alinhado aos 6 canais LP do BACEN para não abrir mais conexões Aurora que o necessário; 6 por nó já processa ~85/s): `backend/config/runtime.exs:731-758`.

### 1.9 Locks: PG advisory por conta no dinheiro; Redis para intent/cache, não para lock de dinheiro

- Serialização do PIX-OUT por conta = `pg_advisory_xact_lock(band(account_id, 0x7FFFFFFFFFFFFFFF))` DENTRO de `Repo.transaction` (bloqueante, não `pg_try_*`, por política do projeto §D2): `backend/lib/fluxiq/use_cases/payments/pix_out/balance_check.ex:113-151, 410-414`; aquisição no orquestrador: `backend/lib/fluxiq/use_cases/payments/outbound_payment/orchestrator.ex:281-301`. Mesma chave usada por MED (`processor.ex:403`, `manual_cautelar_block.ex:146`, `unfunded_drainer.ex:191`) e tesouraria (`caixa_aviv.ex:76`).
- Otimização em curso (flags, default OFF): tirar TODO o I/O TigerBeetle de dentro do lock. Etapa 1b (`PIXOUT_INTRANSIT_PG_ENABLED`): in_transit passa a vir de SUM PG de `outbound_requests` em vez do `debits_pending` TB dentro do lock; Etapa 2 (`PIXOUT_TB_OUT_OF_LOCK_ENABLED`): `create_transfers` sai da transação lockada (o TB round-trip de ~68ms/p99 1235ms era 48%/85% do hold do lock); Etapa 4 (`PIXOUT_LOOKUP_PRELOCK_ENABLED`): snapshot de saldo TB pré-lock, lock vira só PG + computação pura (poucos ms): `backend/config/runtime.exs:202-235`.
- Redis: intent pendente do PIX-in fase 1 -> fase 2 (chave `pix_in_pending:` TTL 600s, GETDEL via script Lua): `handler.ex:39-41`; cliente unificado Redix/RedixClustered: `backend/lib/fluxiq/infra/redis.ex:1-50`. Idempotência HTTP em ETS de vida longa (GenServer dono da tabela, TTL 24h): `backend/lib/fluxiq/infra/idempotency_cache.ex:1-45`. Dedup de mensagem = ETS read-through + PG UNIQUE: `message_dedup.ex:1-49`. Conclusão: o lock de dinheiro é PG advisory (transacional, se auto-libera no commit/rollback); Redis nunca é a fonte de serialização financeira.

### 1.10 Como isso sobrevive a 1,53M tx/dia no PG

1,53M/dia com pico noturno (dado do mandato) dá média de ~18 tx/s e picos plausíveis de centenas/s. O desenho de dados que aguenta isso, todo evidenciado acima:

- tabela quente append-only em estado final (sem UPDATE), MERGE idempotente com pruning por partição, PK composta determinística em retry;
- agregado por trigger com 128 buckets (sem hot row), compactado de madrugada;
- leitura de saldo O(1 linha + 1h de delta) via checkpoint horário (a versão diária derrubou o writer em 2026-07-08);
- bulk em pool separado (`fluxiq_batch`) e leitura pesada no reader (`fluxiq_read`) com fallback instrumentado;
- oban_jobs reindexado diariamente e crons defasados;
- TTL/purga batelada nas tabelas de crescimento livre; retenção regulatória por DROP de partição, nunca DELETE;
- autovacuum por leaf calibrado; `application_name` por pool para atribuição em `pg_stat_activity`.

---

## 2. Como a Monetarie faz

### 2.1 Core (mon_core): mesmo DNA de dados, menos malha de escala

O Core da Monetarie compartilha a arquitetura de dados da família (é visível no código que módulos foram portados da mesma linhagem):

- `transactions` JÁ NASCE particionada por RANGE(`started_at`) com PK composta `(id, started_at)`, id BYTEA 16 bytes e partições y2026m01..+default no dump de produção aplicado pela squash migration: `core/backend/priv/repo/sql/mon_core_schema_clean.sql:4084-4114` (parent) e 4116+ (leafs); a análise da Task A2 confirma PK `transactions_part_pkey`, zero FKs referenciando `transactions(id)` e que shadow/cutover seria churn sem ganho: `core/backend/priv/repo/migrations/20260704133000_extend_message_and_fee_partitions.exs:7-33`.
- `account_entries` (espelho de extrato do TB) e `cosif_journal_entries`: convertidas para particionadas RANGE(`entry_date`) via tabela-sombra + copy + RENAME swap, PK `(id, entry_date)`, partições 2024-2027: `core/backend/priv/repo/migrations/20260308400004_partition_journal_entries.exs:18-95`; estendidas a 2028 em `20260308500003_extend_partitions_2028.exs` (citada em `partition_maintenance.ex:41`).
- Runway das 3 tabelas que ficavam fora (port-aviv Task A2): `message_timeline` (received_at), `fee_transactions` (created_at), `fee_split_transactions` (charged_at) ganharam partições 2026-01..2028-12 + DEFAULT, pulando ranges já cobertos por partições de convenção legada `{tabela}_YYYY_MM` (checagem por `pg_inherits`/`relpartbound`, não por nome): `20260704133000` (moduledoc completo, linhas 1-50).
- `Monetarie.Workers.PartitionMaintenance`: cron diário 01:00, 8 tabelas (`transactions inflow_requests outflow_requests account_entries cosif_journal_entries message_timeline fee_transactions fee_split_transactions`), `months_ahead 0..3` (inclui o mês CORRENTE, para tabela recém-coberta ganhar a partição vigente já na primeira execução), gate `relkind='p'` com schema qualificado `public.` (defesa contra homônimos `cecresa_*` do laboratório), e a mesma verificação de herança de índices: `core/backend/lib/monetarie/workers/partition_maintenance.ex:53-113` (e 115-120 para o reuso público pelo ETL criar partições históricas).
- Autovacuum overrides portados: `core/backend/priv/repo/migrations/20260427190000_autovacuum_per_table_overrides.exs` (existência verificada por grep; conteúdo não lido linha a linha nesta trilha).
- `account_daily_summaries` com `bucket SMALLINT` e UNIQUE `(account_id, day, type, direction, bucket)`: `core/backend/priv/repo/migrations/20260314220000_create_account_daily_summaries.exs:10-15`; trigger + `bucket_for_tx` 0-127 portados byte a byte, incluindo o comentário de que o worker de compactação reduz N buckets a 1 de madrugada: `core/backend/priv/repo/migrations/20260429170000_create_daily_summary_trigger.exs:14-45, 76-114`.
- Stored procedures do hot path portadas: `20260429100000_create_upsert_pix_in_transaction_proc.exs` e `20260429130000_create_insert_failed_transaction_proc.exs` (nomes no listing de migrations).
- `BalanceGuard` com `min(TB, PG)`: lado PG soma `account_daily_summaries` (rollup populado por trigger) + `fee_split_transactions` ao vivo, menos `outbound_requests` stages 0/1/2 in-flight: `core/backend/lib/monetarie/use_cases/payments/balance_guard.ex:245-300`.
- `BalanceCheckpoint` PORTADO atrás de flag `BALANCE_CHECKPOINT_ENABLED` (default OFF), com duas diferenças estruturais em relação à fonte: (a) o fold é sobre `account_daily_summaries` (não sobre partições de `transactions`), fronteira em DATE do dia BRT; (b) ANTI-RETRO-DATAÇÃO explícita: o trigger mantém `updated_at` na summary, cada fold grava watermark, e o hot path/prefold detecta summary retro-escrita (`day < through_day AND updated_at >= watermark - 60s`) e faz rebuild integral da conta. Motivado por caso real nosso: outbound in-flight por horas cruzando a meia-noite BRT (outage HSM 2026-07-03) gravaria débito permanentemente fora do checkpoint E fora do delta, superestimando saldo: `core/backend/lib/monetarie/use_cases/payments/balance_checkpoint.ex:1-85` e migration `20260704140000_create_account_balance_checkpoints.exs` + `20260704180000_add_updated_at_to_account_daily_summaries.exs`.
- MAS: o prefold da Monetarie roda SÓ 1x/dia (03:10 UTC, `{"10 3 * * *", Monetarie.Workers.BalanceCheckpointPrefold}`): `core/backend/config/runtime.exs:464-468`. A AVIV aprendeu na pele (teto ACU 2026-07-08) que fold diário = writer proporcional a volume ao quadrado no rush; a diferença aqui é que a fronteira da Monetarie é dia BRT sobre SUMMARIES (o delta vivo lê no máximo ~2 dias de linhas de summary por conta, não a fatia bruta do dia em `transactions`), o que amortece bastante o quadrático, porém o número de linhas de summary por conta/dia cresce com buckets*types (até 128 buckets por combinação). Ver recomendação P1.
- Auditoria: `Monetarie.Repo` é wrapper transparente sobre `Monetarie.Infra.Repo.Base`; writes emitem audit ASSÍNCRONO via `Task.start` (`:audit_async` default TRUE), leituras e bulk sem overhead, campos sensíveis redigidos, AuditLog bypassa o wrapper: `core/backend/lib/monetarie/repo.ex:1-100`. Ou seja: mais barato que o default síncrono da AVIV no hot path, porém SEM buffer com spill em disco nem worker de recuperação (Task.start perde o audit se o nó cair ou o PG estiver fora naquele instante). Não localizei AuditBuffer/spill no core (ver seção 7).
- Pools: `POOL_SIZE` default 30 e `statement_timeout` 30s: `core/backend/config/runtime.exs:255, 260`; `TB_POOL_SIZE` default 3: linha 270. Um ÚNICO repo Ecto (`Monetarie.Infra.Repo.Base`: `core/backend/lib/monetarie/infra/repo/base_repo.ex:1`). Não existe ReadRepo nem BatchRepo: o próprio código admite, "MONETARIE does not currently have the separate BatchRepo used by coreproviders": `core/backend/lib/monetarie/use_cases/payments/outbound_payment/lifecycle_log.ex:6`; o `BatchWriter` do core flusha pelo Repo principal ("TODO: BatchRepo not available, using Repo as fallback"): `core/backend/lib/monetarie/use_cases/payment_transactions/batch_writer.ex:147`. Não vi `application_name` configurado em nenhum pool (grep em config retornou só statement_timeout).
- Oban do core: repo Base, Pruner (sem Reindexer, sem Lifeline no bloco de runtime), cron com ~30 entradas SEM defasagem de offset (vários `*/15` e `*/5` alinhados), filas incluindo `nats_publish: 10` (o outbox), `audit: 1`, `pix_in_pg_write: 10`: `core/backend/config/runtime.exs:400-488`.

### 2.2 Caminho canônico do PIX-in no Core (flag OFF, provado em produção 09/07)

Evento NATS `transaction.created` da cabine -> `PixHandler.handle_transaction_ledger` (com defesa F-07 que só atua quando `two_phase_pix_in_enabled` = true; default false = comportamento legado): `core/backend/lib/monetarie/infra/nats/handlers/pix_handler.ex:349-393`. O `do_handle_transaction_ledger` executa, por PIX-in:

1. `audit_log("PIX_TRANSACTION", ...)` (assíncrono): linha 398;
2. `create_pix_journal_entry` (INSERT em `cosif_journal_entries` particionada) INLINE no hot path: linha 400;
3. `create_tigerbeetle_transfer` (TB): linha 401;
4. `Publisher.publish_balance_update` (NATS): linha 405;
5. `maybe_create_inbound_transaction` (INSERT em `transactions`, dispara o trigger de daily summary): linha 409;
6. `StatementEntries.record_inbound_settled` (INSERT em `account_entries`, extrato; idempotente por reference+source, fail-soft): linhas 411-414;
7. `maybe_charge_fee` (tarifa, non-blocking): linhas 416-424.

Total no Core: ~4 INSERTs PG + trigger MERGEs + 1 transfer TB + audit assíncrono + publishes NATS, por PIX-in. Compare com a AVIV que faz 1 stored-proc MERGE + 1 fee row no equivalente (COSIF fora do hot path).

### 2.3 Cabine PIX (monetarie_*): schemas próprios, partição só onde dói, outbox transacional

- Esquemas por domínio (`monetarie_auth/dict/spi/spi_ref/spi_msg/audit/settlement`), tabela canônica de transação = `monetarie_spi.messages` (o schema `Shared.Schemas.Spi.Transaction`), mapa de status 1..10: `pix/CLAUDE.md` (seções Database e Status ID mapping; doc, não código).
- Particionamento na cabine: `monetarie_spi.messages` PARTITION BY RANGE(`operation_time`), mais `monetarie_auth.activity_log` e `login_history` (created_at), mantidas por um GenServer `Shared.PartitionManager` (diário, lookahead 60 dias, validação de identificadores por whitelist, purga de audits expirados via XmlArchiver): `pix/backend/apps/shared/lib/shared/partition_manager.ex:1-80`.
- Hot path pacs.008 recebida (InboundProcessor): dedup Redis primeiro (`check_message_dedup`), depois UMA `Repo.transaction` com `upsert_inbound_in_tx` (acervo `bacen_inbound`) + `create_transaction_in_tx` (messages) + `update_inbound_status("PROCESSED")`, rollback limpo em duplicata: `pix/backend/apps/spi_service/lib/spi_service/workers/inbound_processor.ex:846-909`. Dedup em camada extra com `pg_try_advisory_xact_lock` + checagem + INSERT com ON CONFLICT fallback: linhas 2760-2800. Depois: `update_pi_balance` (Conta PI) e evento para o Core via outbox durável: linhas 917-929.
- Outbox transacional (padrão Wave 6): `Shared.Nats.Publisher.publish_async/2` grava o job Oban (linha de `oban_jobs`) DENTRO da `Repo.transaction` do estado de negócio; `Shared.Outbox.RuntimeGuard` verifica em runtime se o caller de money path está em transação (strict em dev/test = raise `AtomicityViolation`; prod = telemetry + log): `pix/backend/apps/shared/lib/shared/outbox/runtime_guard.ex:1-30` e `atomicity_violation.ex:1-30`; enforcement adicional por Credo AST (`Shared.CredoChecks.OutboxAtomicity`, citado no moduledoc). O InboundProcessor foi refatorado para esse padrão em todos os pontos de emissão (comentários nas linhas 24-72 e usos em 754-758, 1074-1078, 2616-2617).
- Fonte única de status: `Shared.Spi.StatusCodes` com os 10 ids canônicos, semântica de terminais/sucesso, migration `ReseedSpiStatusCodes` re-semeando a tabela de referência e teste de contrato que falha se módulo e banco divergirem: `pix/backend/apps/shared/lib/shared/spi/status_codes.ex:1-30` e `pix/backend/apps/shared/priv/repo/migrations/20260701090000_reseed_spi_status_codes.exs`.
- Pools da cabine: 4 repos (Shared, Spi, Dict, Settlement) TODOS com `pool_size` default 5, `queue_target` 5s, timeout de transação 15s (ANS-compatible), SEM `statement_timeout` de sessão e SEM `application_name`: `pix/backend/config/runtime.exs:215-241`. Total default = ~20 conexões por task; o comentário explicita que é conservador de propósito e manda escalar via `POOL_SIZE`.
- Logs PERF por fase (padrão AVIV) já portados no InboundProcessor (Task B1): `inbound_processor.ex:160`.
- Latência da cabine no ICOM: 24-28ms por GET vs long-poll OnZ p50 471ms (afirmação do comparativo `docs/reports/2026-07-04-comparativo-aviv-coreproviders-monetarie.pdf`; doc, medido em 04/07, não re-medido nesta trilha).

### 2.4 Consistência ponta a ponta na Monetarie

- Cabine e Core são bancos lógicos no MESMO cluster Aurora (HML: cluster único `monetarie-core-homolog`, `pix/CLAUDE.md` cabeçalho; produção 10.50 com Aurora própria, contexto do mandato). Consequência: as escritas da cabine E do Core disputam o MESMO writer.
- A ponte é o outbox Oban -> NATS JetStream -> handlers do Core (PixHandler etc.), com `Nats-Msg-Id` para dedup e DLQ monitorada (`Monetarie.Workers.Nats.DlqMonitorWorker` cron */5: `core/backend/config/runtime.exs:415`).
- Espelho TB->PG: `account_entries` é a projeção de extrato; `transactions` é o registro de negócio; `TbPgReconciliationJob` a cada 15min: `core/backend/config/runtime.exs:417`; `PixStatusReconciliation` a cada 15min materializa status finais presos consultando a cabine por E2E: linhas 418-421.

---

## 3. Diferenças estruturais e por que existem

| Dimensão | AVIV | Monetarie | Por quê |
|---|---|---|---|
| Topologia | 1 app (fluxiq) + 1 banco; PIX via provedor OnZ HTTP | 2 sistemas (cabine PIX + Core) + NATS; BACEN direto mTLS | participante indireto vs direto; a Monetarie carrega a máquina ICOM/DICT/XMLDSig dentro de casa |
| Broker | NATS DESCOMISSIONADO ("NATS subsystem permanently removed": `backend/config/runtime.exs:602`; dedup module comenta o descomissionamento: `message_dedup.ex:13-16`) | NATS JetStream é a espinha (outbox transacional + streams) | AVIV internalizou tudo num app só; Monetarie precisa de fronteira cabine/Core |
| Repos PG | 3 pools (writer 100 / reader 50 / batch 10) com application_name | 1 pool por app (Core 30; cabine 4x5) sem application_name | AVIV escalou sob incidente; Monetarie ainda não precisou |
| Writes por PIX-in no hot path | 1 stored-proc MERGE + fee + inbox/dedup rows (COSIF fora do hot path) | cabine: 3 writes em 1 txn + outbox row; Core: COSIF inline + transactions + account_entries + fee | Monetarie mantém extrato (account_entries) e COSIF em tempo real; AVIV projeta COSIF por catch-up |
| Tabela quente | transactions append-only estado final, sem UPDATE (trigger de update dropado) | cabine `messages` recebe updates de status (status_id 1->2->4 etc., máquina ICOM); Core `transactions` só estado final | modelo direto BACEN exige ciclo de vida da mensagem na cabine |
| Saldo autoritativo | min(TB, PG) com checkpoint HORÁRIO | min(TB, PG) sobre summaries; checkpoint portado, flag OFF, prefold DIÁRIO | port de 04/07; flag ainda não ligada em produção |
| Auditoria | wrapper com INSERT síncrono default + AuditBuffer com spill em disco | wrapper com Task.start assíncrono default, sem spill | trade-offs opostos: AVIV durável e cara; Monetarie barata e com janela de perda |
| Partições | worker Oban 3 meses à frente, 7 tabelas; audit_logs particionada | worker Oban (Core, 8 tabelas, mês corrente + 3) + GenServer na cabine (60 dias) | equivalente; Monetarie tem 2 mecanismos por ter 2 sistemas |
| Hot-row agregado | 128 buckets + compactação noturna | idêntico (portado) | mesma linhagem de código |
| Higiene oban_jobs | Reindexer diário (GIN 3GB de bloat, incidente) | ausente | a Monetarie usa Oban ATÉ como outbox de NATS, churn será maior por tx |

---

## 4. O que a AVIV tem de melhor (candidatos a port), com esforço

1. Fold HORÁRIO do BalanceCheckpoint + rebuild noturno (esforço S). A Monetarie portou o checkpoint com fronteira de dia BRT e prefold 1x/dia (`core/backend/config/runtime.exs:464-468`). A AVIV pagou com um teto de ACU (2026-07-08) para aprender que fronteira larga = custo por chamada crescendo com o dia (`balance_checkpoint.ex:28-43`). Antes de ligar `BALANCE_CHECKPOINT_ENABLED`, encurtar a cadência do prefold (horária) e medir o tamanho do delta por conta no rush.
2. `Oban.Plugins.Reindexer` + monitoramento de bloat GIN de `oban_jobs` (esforço S). Evidência do problema na fonte: `backend/config/runtime.exs:620-624`. Na Monetarie o Oban é também o outbox NATS (`nats_publish`) e o `pix_in_pg_write`; com 1,5M tx/dia o churn de `oban_jobs` supera o da AVIV.
3. ReadRepo (Aurora reader) + `Fluxiq.Reads` com fallback instrumentado (esforço M). Port quase literal de `reads.ex:1-45` + `runtime.exs:326-350`. Tira dashboards, reconciliação, extratos e relatórios do writer. Pré-requisito: reader provisionado em produção (a infra prod da Monetarie tem Aurora 17.9; reader é decisão de infra, fora desta trilha).
4. BatchRepo (pool separado para insert_all) (esforço S). O core JÁ tem o TODO no código (`batch_writer.ex:147` e `lifecycle_log.ex:6`). Sem isso, um flush de lote ou ETL disputa as mesmas 30 conexões do money path.
5. `application_name` por pool + `statement_timeout` de sessão na cabine (esforço S). AVIV: `runtime.exs:318-321, 343-347`. Monetarie: core tem statement_timeout 30s mas sem application_name (`core runtime.exs:255-260`); cabine não tem nenhum dos dois (`pix runtime.exs:219-236`). Sem application_name, qualquer investigação de contenção no Aurora começa cega.
6. COSIF fora do hot path (esforço M/L, decisão de produto). AVIV removeu o journal entry do stored proc do PIX-in e projeta por catch-up periódico (`20260301000020:68-70` + `CosifSyncJob` cron). O Core da Monetarie insere `cosif_journal_entries` inline em cada PIX-in (`pix_handler.ex:400`). Em volume alto isso é +1 INSERT particionado por tx e +1 ponto de falha no caminho do crédito. Requer garantia de reconciliação equivalente antes de mover (regra do dono: provar, não inferir).
7. Rollups por conta para dashboards admin (esforço S/M). Port de `20260628230000` (motivação: conta pesada com 444k tx/90d fazia cards de 200-360ms). Vale para o admin do Core e para telas da cabine que agregam `messages` por janela.
8. Purga batelada por `ctid` com cap + kill-switch para tabelas unbounded (esforço S). Padrão de `partition_maintenance.ex:129-204` (lotes de 5k, sleep entre lotes, filtro pelo índice existente, estados não-terminais intocados). Candidatas na Monetarie: `bacen_inbound`/`bacen_outbound` da cabine, `processed`/idempotency logs, `webhook_deliveries`.
9. Etapas 1b/2/4 do PIX-OUT (TB I/O fora do advisory lock) (esforço M). Flags e racional em `runtime.exs:202-235`. Se o PIX-OUT da Monetarie serializa por conta com lock cobrindo round-trip TB (a verificar na trilha de PIX-out), este é o maior corte de hold de lock disponível.
10. AuditBuffer com spill em disco + SpillRecoveryWorker (esforço S/M). O audit assíncrono do core (`Task.start`) perde eventos em crash/outage de PG; o buffer com spill da AVIV (`audit_buffer.ex:24-41`) fecha essa janela sem voltar ao custo síncrono.

## 5. O que a Monetarie tem de melhor (não regredir)

1. Outbox transacional NATS com enforcement em três camadas (runtime guard + exceção em dev/test + Credo AST): `pix/backend/apps/shared/lib/shared/outbox/runtime_guard.ex`, `atomicity_violation.ex`; espelhado no core (`core/backend/lib/monetarie/outbox/runtime_guard.ex`). A AVIV, sem broker, cobre atomicidade com sweepers e inbox; a garantia estrutural da Monetarie é mais forte para um sistema de 2 processos.
2. Anti-retro-datação no BalanceCheckpoint (watermark `updated_at` + rebuild automático): `core/backend/lib/monetarie/use_cases/payments/balance_checkpoint.ex:56-80`. A fonte AVIV não detecta retro-escrita inline (só se auto-cura no rebuild noturno). O port da Monetarie é superior neste ponto; não regredir ao re-sincronizar com a fonte.
3. PartitionMaintenance mais defensivo: checagem de cobertura de range por `pg_inherits`/`relpartbound` (não por nome), meses `0..3` incluindo o corrente, gate com schema qualificado contra homônimos: `core/backend/lib/monetarie/workers/partition_maintenance.ex:39-46, 69-88, 96-113`. A AVIV quebrou 14 dias por confiar no nome (`partition_maintenance.ex:237-242` da fonte).
4. Fonte única de status com teste de contrato módulo<->banco e reseed por migration: `status_codes.ex` + `20260701090000`. A AVIV usa smallints/strings espalhados (`status SMALLINT`, `payment_status VARCHAR`: `20260301000003:20, 30`) sem contrato equivalente visível.
5. `account_entries` particionada como read model de extrato independente de `transactions`: `20260308400004`. Extrato do IB/admin não compete com o registro de negócio.
6. Latência estrutural: caminho direto BACEN (ICOM GET 24-28ms vs long-poll OnZ p50 471ms; comparativo de 04/07, doc). Nenhuma otimização de dados da AVIV compensa 400ms+ de provedor; essa vantagem é nossa e é de arquitetura.
7. Dois mecanismos de partição cobrindo os dois sistemas (worker Oban no core, GenServer na cabine com whitelist de identificadores): `partition_manager.ex:21-50`.

## 6. Recomendações priorizadas (meta: mais volume, menos latência)

P0 (antes de qualquer rampa de volume):
1. Prefold horário no BalanceCheckpoint do Core + medir delta por conta no pico; só então ligar `BALANCE_CHECKPOINT_ENABLED` em HML com paridade viva (o teste de paridade já existe: `core/backend/test/monetarie/use_cases/payments/balance_checkpoint_test.exs`). Evidência do risco: incidente ACU AVIV (`balance_checkpoint.ex:28-43` da fonte).
2. `application_name` em TODOS os pools (core + 4 repos da cabine) e `statement_timeout` de sessão na cabine. Sem isso não há atribuição de carga no Aurora quando o volume subir. (Fonte: `runtime.exs:318-321` AVIV; lacuna: `pix runtime.exs:219-236`.)
3. Oban Reindexer + alarme de tamanho dos índices GIN de `oban_jobs` no core e na cabine (o outbox NATS transforma cada evento de dinheiro em churn de oban_jobs). Evidência: `runtime.exs:620-624` AVIV.
4. Defasar os offsets do cron Oban do core (hoje vários `*/5` e `*/15` alinhados: `core runtime.exs:406-468`); padrão AVIV `1-59/5`, `2-59/15` etc. Custo zero, remove picos síncronos de leitura no writer.

P1 (escala estrutural):
5. BatchRepo (pool separado) no core e resolver o TODO do BatchWriter (`batch_writer.ex:147`); avaliar pool dedicado para o `nats_publish`/webhooks se a telemetria mostrar fila.
6. ReadRepo + read-split por domínio com fallback instrumentado (port de `reads.ex`), começando por dashboards/extratos/reconciliação. Exige reader Aurora em prod.
7. Tirar o COSIF do hot path do PIX-in do Core (projeção periódica idempotente, com reconciliação provada antes do flip; padrão da fonte `20260301000020:68-70` + `CosifSyncJob`).
8. Pools da cabine: subir `POOL_SIZE` dos 4 repos com base em medição (default 5 é de laboratório: `pix runtime.exs:216-218`) e dimensionar contra o teto de conexões do Aurora junto com o pool 30 do core (mesma instância em HML).
9. Rollups por conta (port `20260628230000`) para o admin não escanear partições em contas pesadas.
10. Política de retenção com purga batelada para `bacen_inbound`/`bacen_outbound`, `webhook_deliveries` e afins (padrão `purge_onz_lp_inbox`), e retenção regulatória por DROP de partição (nunca DELETE) para trilhas de auditoria, como a AVIV formalizou para `audit_logs` (`partition_maintenance.ex:31-43`).

P2 (aperfeiçoamento):
11. AuditBuffer com spill em disco + SpillRecoveryWorker no core (fecha a janela de perda do `Task.start` sem custo síncrono).
12. Aplicar autovacuum storage params nas partições NOVAS dentro do PartitionMaintenance (fazer melhor que a fonte: nem a AVIV fecha esse gap; evidência: ausência de ALTER TABLE SET no worker da fonte vs comentário `20260421160001:17`).
13. Avaliar particionar `audit_logs` do core quando o volume justificar, usando o playbook shadow-table da AVIV (`20260621000000`), que é o melhor exemplo de online cutover do par de repositórios.
14. Se o PIX-OUT da Monetarie mantém I/O TB sob advisory lock, portar as Etapas 1b/2/4 (flags) da fonte (`runtime.exs:202-235`).

## 7. Perguntas abertas / NÃO VERIFICADO

1. Recorde de 1,53M tx/dia e Aurora PG 17.9 da AVIV: contexto do mandato (medições de trilhas anteriores/produção), não verificável por código neste repo local. Nesta trilha usei apenas como ordem de grandeza.
2. FKs apontando para `transactions` na AVIV: verifiquei a ausência no lado Monetarie (afirmado com grep em `20260704133000:26-28`); não fiz a mesma prova no dump da AVIV (não há dump schema-only no repo; as migrations não criam FK para transactions, mas não é prova exaustiva).
3. Valores REAIS de POOL_SIZE/READ_REPLICA_URL/flags nas task-defs de produção dos dois lados: exigiria AWS (proibido nesta trilha). Os defaults citados são os do código.
4. Se `monetarie_spi.messages` sofre UPDATE de status em taxa relevante (bloat/HOT churn) no volume alvo: o modelo de máquina de estados implica updates (status_id 1..10), mas não medi taxa nem fillfactor; vale um `pg_stat_user_tables` em HML antes de tunar autovacuum da cabine.
5. Retenção efetiva de `monetarie_audit` (10 anos declarados no `pix/CLAUDE.md`) e tamanho real das tabelas de acervo em produção: precisa runtime.
6. AuditBuffer/spill no core: afirmei ausência com base em grep (`audit_async` + Task.start em `repo.ex:7-10`); se existir mecanismo equivalente em outro namespace, a recomendação 11 cai.
7. O comparativo de latência 24-28ms vs 471ms é do PDF de 04/07 (`docs/reports/2026-07-04-comparativo-aviv-coreproviders-monetarie.pdf`); não re-medi nesta trilha.
8. `bacen_inbound`/`bacen_outbound` da cabine: não li o DDL completo nesta trilha (afirmações sobre elas vêm do uso no InboundProcessor); a recomendação 10 exige mapear índices e volumetria antes de purgar.
