# Plano: paridade LegadoPIX/LegadoSPB + integrações cabines-Core (frente de 04/07, noite)

> **For Claude:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development (implementador, spec reviewer independente, quality reviewer, fixes por TDD), mesmo método do port AVIV de hoje.

**Goal:** fechar os itens ABERTOS das matrizes de paridade PIX e SPB (aterrados em 04/07 contra o código real, não pelo status escrito) e as lacunas de integração cabine-Core, terminando com validação viva T1-T5 em HML.

**Base de aterramento (fonte de cada âncora abaixo):** os três relatórios de agentes de 04/07 (matriz PIX aberta, matriz SPB aberta, mapa de integrações), derivados de `docs/reports/2026-07-02-paridade-{pix-legadopix,spb-legadospb}.md` verificados item a item no código atual.

**Decisões do dono (04/07, gravadas):**
1. **Contabilidade SEPARADA mas INTEGRADA**: o monorepo será dividido por sistema; clientes white-label usarão só as cabines. Cada cabine mantém trilha contábil própria funcional standalone; a integração com o Core (ex.: `cosif_to_core`) existe por flag. PROIBIDO remover a trilha da cabine; PROIBIDO deixar falha contábil silenciosa (`_ -> :ok`).
2. Netting da cabine PIX: MANTER documentado (não remover).
3. `verify_dict`: LIGAR a verificação de assinatura das respostas DICT.
4. SOS-envio e conversão PAG-STR: DEPOIS (registrar, não implementar agora).

**Regras (herdadas do plano AVIV, seção 0):** nunca inferir (âncora não bate = para e reporta); TDD; flags para mudanças de comportamento em produção; commits pt-br sem travessão; PIX em `pix/backend`, SPB em `spb/`, Core em `core/backend`; centavos na borda do Core, subcentavos internos conforme ADR-001; TigerBeetle intocável.

---

## Onda S: SPB (árvore `spb/`)

- **S1 [P0 dinheiro] Predicado financeiro:** `engines/message_type_driver.ex:64,77` testa `financial_flag == "S"`, mas o domínio real é F/N/D/T/A/C (zero "S" no banco): a previsão de saldo/contábil do LifecycleEngine está cega. Corrigir para o domínio real (como o PostIntegration lê), normalizar no loader `:296`, testes com os 6 valores. Aceite: previsão de saldo dispara para tipos F/D e não dispara para N/T/A/C.
- **S2 [P1] Regressão GEN0018:** `day/day_auto_close.ex:9,13,271-275,335,356` ainda usa GEN0017/0018 como abertura/fechamento de dia; catálogo diz que dia real é STR0016/STR0017 (o fix de 02/07 corrigiu só o CoaCodHandler). Trocar o gate; testes.
- **S3 [P1] value_tag com colchetes:** `message_type_driver.ex:150` devolve tag crua `[VlrTit]` etc. (39+ tipos caem em `:no_amount`, sobrevivendo só pelo fallback). Strip de `[ ]` como `field_access.ex` faz; teste com os 3 exemplos reais.
- **S4 [P1] Poison/backout no sidecar MQ:** `bacen_gateway_mq_sidecar/.../Sidecar.java` sem `JMSXDeliveryCount`/backout: webhook não-2xx = redelivery infinita a cada 5s. Ler o delivery count e mover para quarentena (fila/log + alerta) acima de N (default 5). Java; testes conforme infra do sidecar permitir, senão prova manual documentada.
- **S5 [P1] Operação:** fiação de GEN0003 (resync) e GEN0012 (cópia) como comandos admin (builders prontos, sem rota); destravamento por operação: hold/release por operação individual, voltar-status (reusar `validate_transition`). SOS-envio NÃO (decisão 4).
- **S6 [P1] R-legs 5.11:** PRIMEIRO conferir contagens no Aurora HML (as do relatório são locais); depois semear os ~49 legs `direction_flag='R'` + tag definitions faltantes.
- **S7 [P1] Contabilidade separada-mas-integrada (decisão 1):** hoje 3 trilhas coexistem sem nenhuma operante (`post_integration/dispatcher.ex:318,336` flag off). Desenhar e implementar: trilha da cabine SPB coerente e fail-fast standalone; integração `cosif_to_core` por flag para quando o Core estiver presente; aposentar apenas a trilha redundante interna (AccountingEngine vs COSIFEngine: consolidar em UMA da cabine). Task começa com mini-design de 1 página para aprovação do dono antes de codar.

### Follow-ups S2/S3 (registrados pela revisão de qualidade)

- **S2-FU1 [P1]**: `store_closing_balance/2` (`str/inbound_processor.ex:693-695`) termina em `rescue _ -> :ok`; pós-S2 essa gravação É o gate de fechamento do dia: falha silenciosa = dia nunca fecha automático, invisível ao operador (fail-closed, mas cego). Trocar por log ERROR + alerta; fere a regra do dono de falha silenciosa.
- **S2-FU2 [P2]**: `check_clearings_settled` (`day_auto_close.ex:222-226`) em exceção devolve todos os clearings settled=true (erro de banco REMOVE um blocker; fail-safe invertido). Alinhar ao padrão do gate STR0016 (exceção = blocker presente).
- **S2-FU3 [P2]**: teste do force_close assertar `{:ok, %{closed: true}}` além do COUNT=0 de GEN0017; consolidar `CoaCodHandler.process_day_closing` (sem callers) com o FileTransfer.Orchestrator; `SPB.ControlMessages.@coa_cod_types` ainda lista GEN0014-18 como coa_cod.

## Onda P: PIX (árvore `pix/backend`)

- **P1 [P1] Extrato/leituras na tabela errada:** `statements.ex:50/134` usam `Transactions.list_transactions` (tabela `monetarie_spi.transactions`, quase vazia) em vez de `messages` (canônica). Apontar statements/balances/controllers para `messages` via `Shared.Schemas.Spi.Transaction`; mapear TODOS os leitores da tabela errada por grep; aposentadoria da tabela é fase 2 (só depois de zero leitores).
- **P2 [P1] D1 residual, devolução de saída sem tracking:** `return_processor.ex:168-215` publica evento sem inserir linha em `messages` para a pacs.004; a pacs.002 de confirmação casa pelo e2e do pagamento ORIGINAL. Criar registro da devolução (RtrId/e2e próprio) e correlacionar a confirmação pela devolução. Fonte legado: AutBank `ANOTACAO_CREDITO.COD_DEVOLUCAO_PACS004`.
- **P3 [P1] D3, correlação camt.060-camt.053/052:** `camt060.ex:54/127` não persiste id de requisição; `inbound_processor.ex:1849-1850` comenta literalmente o gap. Persistir id na emissão e casar em `process_eod_statement`/`process_account_report`.
- **P4 [P1] Alçada no pipeline de envio:** grep de alçada em `outbound_sender.ex`/`core_event_processor.ex` vazio; só endpoint advisory. Gate `pending_alcada` antes do OutboundSender + limite acumulado com data-base (legado: WorkerPosAlcada). **ENTREGUE (decisão de desenho):** o gate da CABINE (`Shared.Alcada.SendGate`, padrão SPB `SendGate`/`AlcadaEngine` 17acab31) aplica-se ao ENVIO MANUAL/admin (tela Construir Mensagem, `MessageController.send_message`) — o fluxo automático do Core (payment_request, ANS 1,6s) já passa por limites/alçada no Core (Onda 1) e é isento por origem; flag `ALCADA_PIPELINE_ENABLED` default DESLIGADO (com flag off o envio é byte-idêntico; ligar exige decisão do dono). Retenção em `monetarie_spi.alcada_held_messages` (`awaiting_approval`, estado próprio — sem `status_id` novo no mapa canônico) + `alcada_registrations`/`alcada_vistos`; `accumulated_amount` (antes ignorado em `SpiService.Alcadas.check_and_register/4`) agora conta: acumulado OUTBOUND do dia (data-base `movement_date`) + valor > teto → retém. Aprovação 4 olhos em `POST /api/v1/messages/held/:id/approve` (aprovador != criador → senão 403 `cannot_self_approve`, legado CRK status 32); ao completar vistos despacha pelo MESMO funil canônico (`dispatch_message/3`). `OutboundSender` filtra explicitamente `"alcada_status" => "awaiting_approval"` (defesa em profundidade, nunca assina/envia retida).
- **P5 [P1] Watchdog mata long-poll saudável:** `icom/health_monitor.ex:64` julga liveness por `GenServer.call` 5s e mata worker em long-poll. Heartbeat via ETS antes/depois do `Finch.request` (legado: WorkerEntradaAPIBase.cs:46-92).
- **P6 [P1] Echo pibr.001 periódico:** sem worker de echo (só manual). Worker por canal (5 min) com evidência persistida e resultado alimentando o CircuitBreaker/half_open (legado: TESTE_CONECTIVIDADE).
- **P7 [P1] Baldes DICT client-side:** contador Redis por balde + penalidade por resposta + restituição de ficha no settled (legado: AB_..._BALDES_DICT; referência de design também no dict_bucket do coreproviders).
- **P8 [P1] Motor de alertas persistente:** catálogo + limiares + histórico + ack real + email (Swoosh se já houver no umbrella; senão registrar canal disponível); substituir o derived-only de `monitoring_controller.ex:125`. Inclui alerta de vencimento de cert e do StuckOutboundChecker.
- **P9 [P1] Duas honestidades funcionais:** StatisticsController real (`statistics_controller.ex:30,90` são mock) usando dados vivos; OTP de posse (5 endpoints 501 em `ownership_controller.ex:12-61`) com e-mail/SMS conforme infra existente na cabine (verificar; se não houver provedor SMS na cabine, e-mail + TOTP e reportar).
- **P10 [P2] Conciliação e saldo diário:** conciliação 3 vias real (messages liquidadas vs camt.053/052 baixados) + snapshot EOD de saldo por data + opening balance real (`statements.ex:447-450` hardcoded 0.00).
- **P11 [P2] Honestidade e decisões aplicadas:** remover latência fabricada (`monitoring_controller.ex:305` strong_rand_bytes) e ack no-op; popular `return_data` no `record_status_history` (`status_updater.ex:620`); **ligar verify_dict** nos pontos de resposta DICT (decisão 3); contabilidade da cabine fail-fast com COSIF semeado, mantendo trilha própria (decisão 1); documentar netting como mantido (decisão 2).

### Follow-ups S5 (registrados)

- **S5-FU1 [P1]**: colunas `sit_lanc_str`/`pending_since` de spb_operations consultadas pelo PendingQueue NAO existem em nenhuma migracao (schema out-of-band no banco vivo); criar migracao formal idempotente.
- **S5-FU2 [P2]**: retry_operation re-submete via FlowOrchestrator sem re-checar on_hold (hoje inalcancavel para held; defesa em profundidade).

### Follow-ups P5/P6 (registrados)

- **P5-FU1 [P2]**: worker :unknown persistente (heartbeat ausente, cenario duplo-crash ETS+deadlock) so gera Logger.debug; escalar para warning+telemetry apos grace period de N ticks.
- **P5-FU2 [P3]**: housekeeping de entradas ETS orfas de pids mortos (shutdown sem terminate).
- **P6-FU1 [P3]**: opcional, nao reportar ao breaker falha LOCAL de assinatura do echo (distinguir de falha do canal); hoje aceitavel por flag off + half_open.

### Follow-up S6 (registrado)

- **S6-FU1 [P1]**: alem dos 49 R-legs semeados, o diff legado x HML vivo revelou 65 legs BASE ausentes do catalogo (CCS0001-0012, SEL, TES01xx, CIR, LDL, LFL, CAM, LPI0007/0008, SRC0001, SCG bases): o relatorio de 02/07 contava 983/1026 presentes mas o vivo tem 918/1026. Derivar e semear com a mesma metodologia de evidencia tripla do S6.

### Follow-ups S7e (registrados)

- **S7e-FU1 [P2]**: `spb_cnt_contas.cd_conta` sem unique (PK=id_conta); hoje 714 cd_conta únicos no CSV real, mas se ganhar duplicata o LEFT JOIN do balancete infla os SUM. Adicionar unique em cd_conta como cinto.
- **S7e-FU2 [P1, PRÉ-DEPLOY]**: a migração 20260705130000 (unique parcial RD/RC) NÃO deduplica; entre S7a e S7e o reverse_posting era check-then-insert sem ON CONFLICT, então pode haver perna RD/RC duplicada em HML -> o CREATE UNIQUE INDEX FALHARIA no deploy. Rodar antes via rpc: `SELECT reversal_of_id, leg, count(*) FROM accounting_entries WHERE reversal_of_id IS NOT NULL AND leg IN ('RD','RC') GROUP BY 1,2 HAVING count(*)>1`. Se houver, limpar antes de migrar.
- **S7e pendências de dono/OPS (não-código)**: arquivo COSIF posicional (é exigido? BACEN vs interno?), drop das tabelas congeladas (accounting_scripts/cosif_entries/spb_chart_of_accounts/spb_accounting_events com count vivo antes), remoção do módulo STR.COSIFEngine (dead code, adiada ao mesmo ciclo do arquivo posicional).

### Follow-ups do workflow de revisão (05/07)

- **P9a-FU [P3]**: auto-PIX conta em dobro nas métricas de pessoa (debtor==creditor==tax_id soma em received E sent); key_fraud_indicators sem filtro success_ids (sobre-inclusão segura p/ fraude, mas diverge do texto); máscara de chave não usa tipo inferido quando key_type omitido. Endpoint advisory, sem dinheiro. Registrar o follow-up antifraude (nenhum decisor consome os números) no handoff.
- **I2-FU [P3]**: DICT_SOURCE_DATABASE_URL ainda na task-def core-api (ociosa; só a mix task offline usa); remover quando conveniente. Telemetria do reconciliation subconta backlog no halt por :not_configured.
- **I3-FU [P2]**: NPC e STA consumers mantêm o extract_msg_id antigo (is_map only) e seguem com dedup inerte (Gnat entrega lista) — mesma classe do I3, fora do escopo PIX/SPB; aplicar MsgIdentity neles. Caminho de retry ({:retry,...}) não re-checa dedup antes de reprocessar (janela pré-existente de duplo processamento).
- **P8-FU [P4]**: ordem de cláusula de current_operator/1 sombreia fallbacks de current_user_id (teórico; sub sempre string hoje).
- **I1-FU-PIX [P2]**: o PixStatusReconciliation existente tem a MESMA fraqueza de starvation do batch (list_stale sem filtro type + limit antes do filtro client-side); o fix do list_stale do I1 deve cobrir os dois workers.

### Follow-up P11 (achado, pré-condição de deploy)

- **P11-FU1 [P1]**: `monetarie_settlement.chart_of_accounts` da cabine PIX tem 0 contas no Aurora HML (conferido vivo 05/07). Criar seed do plano COSIF da cabine PIX (~44 contas + accounting_events) — simetria com o Roteiro do SPB. Sem ele, o fail-fast do P11 emite accounting_failure em cada evento (correto, mas ruidoso). Tabelas existem, só falta o seed.

### Follow-ups P2 (registrados pelo re-review)

- **P2-FU1 [P2]**: idempotência do journal de estorno tem entry_date na chave (replay de DLQ dias depois criaria segundo estorno); mesmo modelo pré-existente de toda materialização PIX no Core (simétrico), registrar e tratar em conjunto quando a janela de idempotência do Core for revisada.
- **P2-FU2 [P3]**: se o RETURN_CREATED um dia carregar campos resolvíveis de conta, a materialização criaria transfer TB mas o payload mínimo do return.rejected não resolveria (estorno TB skip); blindagem futura = evento de rejeição carregar o json_input inteiro.

### Follow-ups P4 (decisões conscientes registradas)

- **P4-FU1 [P3]**: mensagem RETIDA por alçada não aparece no saldo projetado nem no extrato — não é linha em `monetarie_spi.messages` (retida ainda não é obrigação: pode ser rejeitada e nunca existir para o BACEN); visibilidade operacional só via `GET /api/v1/messages/held`. **P4-FU2 [P3]**: envios manuais liberados não alimentam o acumulador (não criam `payments`); o teto acumulado protege contra o VOLUME do fluxo do Core no mesmo `message_code`. O bucket do acumulado é dia-UTC — a mesma semântica dos escritores de `movement_date` (`Date.utc_today()` em `transactions.ex:65`, `core_event_processor`, `return_processor`, `payment_controller`); mudar para BRT exigiria mudar TODOS os carimbos (decisão futura). A aprovação/rejeição da retida é por CAS (`UPDATE ... WHERE status='awaiting_approval'`, contagem decide): só o vencedor despacha; o perdedor recebe `already_processed` (fim do risco de duplo despacho ao BACEN).

## Onda I: Integrações (Core + cabines)

- **I1 [ALTA] SpbStatusReconciliation no Core:** espelho do `PixStatusReconciliation` (`*/15`) para TEDs SPB presos em `processing`, consultando a cabine SPB (definir superfície: API admin da cabine SPB ou evento de replay; ler o que a cabine expõe antes).
- **I2 [ALTA] CabinStatusLookup fail-closed:** remover o fallback SQL (`cabin_status_lookup.ex:309`, `@sql :32-78` via `DICT_SOURCE_DATABASE_URL`); sem API configurada = erro claro + alerta, nunca SQL direto no banco da cabine.
- **I3 [MÉDIA] Dedup determinístico sem header:** `pix_consumer.ex:301-303`/`spb_consumer.ex:279-281` geram id por monotonic_time quando falta `Nats-Msg-Id`; trocar por chave de negócio determinística (hash de subject+event+e2e/num_ctrl).
- **I4 [MÉDIA] Evento legado `monetarie.spi.status.rejected`:** produtor `status_updater.ex:298-305`, zero consumidores no Core; verificar consumidores externos (grep em todo o monorepo + docs Partner) e, se zero, parar de emitir.
- **I5 Validação viva T1-T5 em HML** (roteiro do mapa de integrações): T1 entrada PIX creditando com dedup provado; T2 rejeição visível sem vazamento; T3 crédito SPB idempotente por num_ctrl_str; T4 PixStatusReconciliation com caminho API provado (não SQL); T5 TbPgReconciliation. Executar após os deploys desta frente; screenshots/queries como evidência (regra 11).

## Sequenciamento

1. S1 + P1 em paralelo (P0 dinheiro SPB; defeito de dados visíveis PIX).
2. Cascata por árvore: S2/S3 juntos (mesmo driver), depois S4, S5, S6; P2, P3, P4, P5/P6, P7, P8, P9, P10, P11.
3. I1-I4 quando as árvores liberarem (I2/I3/I4 tocam core+consumers; I1 core).
4. S7 (contábil) após mini-design aprovado.
5. Deploy HML do pacote + I5 validação viva + handoff.

## Fora desta frente (registrado)

SOS-envio, PAG-STR (decisão 4); MES01 :12522 (RTM); provas E2E dependentes do BACEN homolog cooperar; envio PIX real (HSM RTM fora); P3s da matriz PIX (gzip, multipart, TPS, tiering); decisões restantes das listas de 04/07 que não bloqueiam tasks desta onda.

## Follow-ups S1 (registrados na revisão de qualidade de 04/07)

Da revisão da Task S1 (predicado financeiro + direção do post de saldo). Registrados aqui para não se perderem; nenhum bloqueia a onda:

- **S1-FU1 — R1-confirmed-timing:** hoje a fase `:confirmed` do saldo é carimbada na perna do R1 (o poster incondicional `InboundProcessor.update_balance_from_r1` e o `LifecycleEngine.update_balance_on_r1` postam `:confirmed` no R1 ACCP); o post do engine no R2 (`update_balance_confirmed`) vira no-op pelo dedup do ledger `balance_movements` (mesma tripla `operation_id/confirmed/direction`). Decidir qual perna DEVE carimbar `confirmed` (R1 = aceite vs R2 = liquidação) exige caracterização do legado — PENDENTE; até lá o comportamento atual fica congelado pelos testes de caracterização.
- **S1-FU2 — terceira cópia do mapa de clearing:** `spb/services/bacen_gateway/lib/bacen_gateway/message_pipeline.ex:272-286` (`extract_clearing_group/1`, usado no credit-check) é a terceira cópia do mapa família-de-clearing, com um ramo extra `TES -> "TES"` que `BalanceGroup.clearing_family/1` não tem (lá TES cairia no default "STR"). Delegar para `BalanceGroup.clearing_family/1` depende de decidir o destino do ramo TES (adicionar TES ao mapa canônico ou aceitar a migração para STR no limite de crédito) — decisão TES pendente.
- **S1-FU3 — tarifa com a mesma doença de grupo:** `lifecycle_engine.ex` (`calculate_and_record_tariff`) usa `o.domain || extract_domain(message_type)` como `clearing_code` — `o.domain` carrega o domínio do envelope RSFN ("SPB01"/"MES01"), não a família de clearing; é a mesma doença que o post de saldo tinha antes de S1. Corrigir para a resolução canônica (`BalanceGroup.clearing_family/1` ou equivalente do TariffEngine) com caracterização das regras de tarifa por grupo antes.
- **S1-FU4 — semântica predicted-não-revertido: FEITO** (documentada no moduledoc de `BacenGateway.STR.BalanceTracker`, seção "Semântica: predicted NÃO é revertido"): lançamentos `:predicted` nunca são estornados; confirmação/rejeição soma em colunas próprias e o fechamento (`totalize_day/1`) ignora as predicted.
