# Handoff FINAL: paridade LegadoPIX/LegadoSPB + integrações (frente 04-05/07)

Implementação COMPLETA: 28 tasks fechadas, cada uma com implementador → spec reviewer independente → (quando toca dinheiro) verificação adversarial → fixes por TDD vermelho-antes. Branch `feat/spb-cert-selfservice`. **STATUS: PUSHADO e DEPLOYADO em HML em 2026-07-05** — `origin/main = 91adf826` (55 commits desde `f285c6e5`, contando este handoff; a frente eram 54 antes do commit de deploy). Detalhe do deploy na seção "DEPLOY EXECUTADO EM HML" abaixo. Plano: `docs/plans/2026-07-04-paridade-legado-pix-spb-integracao.md`; design contábil: `docs/plans/2026-07-04-s7-contabilidade-spb-design.md`.

## O método pagou: defeitos que só a revisão independente pegou

O ciclo de dupla revisão + verificação adversarial (incl. um workflow de 11 agentes em `wf_9b6a76a7-dc4`) achou defeitos reais que os testes dos próprios implementadores passavam:
- **I1**: reprocessar TED liquidada **re-cobrava a tarifa** (duplo débito TB) — eliminado com guarda de regressão terminal.
- **P9b**: lockout de OTP furável — primeiro fix deixou a via **EXPIRED** (palpites lavados), fechada num 2º re-review adversarial com PoC.
- **P4**: dois aprovadores simultâneos = **duplo despacho** ao BACEN — CAS.
- **S2**: `force_close` mandava **GEN0017 espúria ao BACEN** — direção/evento errados.
- **S1**: tipos de crédito postavam previsão de saldo com **sinal invertido** — direção pelo catálogo.
- **S5**: worker `PendingQueue` reenviava STR **furando o hold** do operador.
- **S4**: decode fail-open no endpoint de quarentena = **perda de payload** — fail-closed 400.
- **P10**: a conciliação antiga lia `messages.xml_content` (**coluna inexistente**) → sempre simulava.
- **P1**: ACCC de entrada sumia do extrato e **dobrava no saldo projetado**.

## Tasks (commit final de cada)

**PIX** — P1 extrato canônico (`cfa01743`); P2 tracking pacs.004 + estorno no Core (`8434f19c`); P3 correlação camt.060 (`37b169e5`); P4 alçada envio manual, flag OFF (`feb88230`); P5 heartbeat watchdog (`30b34ffc`); P6 echo pibr.001, flag OFF (`031bb5ff`); P7 baldes DICT advisory (`f6caaba1`); P8 alertas persistentes (`f2f21a0e`); P9a statistics reais (`5cf82125`); P9b OTP posse (`14c01176`); P10 conciliação 3 vias + snapshot EOD (`6485c354`); P11 honestidades + fail-fast contábil cabine (`59f06cf1`).

**SPB** — S1 predicado financeiro (`bccd8172`); S2 DayAutoClose STR0016 (`fd7c54bb`); S3 value_tag (`ca28079f`); S4 poison sidecar MQ (`4f64909e`); S5 comandos operação + hold (`a185b1c7`); S6 R-legs 5.11 (`b2c817cc`); S7a consolidação contábil canônica (`b29f7ea2`); S7b fail-fast 7 sites (`6c860ec2`); S7c espelho cosif_to_core no Core, flag OFF (`94b55ada`); S7d seed COSIF cabine PIX + migração tabelas (`aca50481`); S7e balancete/reverse concorrente/seeds mortos (`410c8e31`).

**Integrações** — I1 SpbStatusReconciliation, sem duplo-fee (`0045816f`); I2 CabinStatusLookup fail-closed (`5d55f8bd`); I3 dedup NATS determinístico (`8b631ff2`); I4 remove spi.status.rejected (`d0cc693f`).

## Verificação consolidada (seed 0)
- pix spi_service **634/0** (limpo); shared 1211/15, dict 304/1, settlement 700/25 — todas pré-existentes (settlement isolado = 17; as 8 extras são CobV/Payload flaky por sandbox shared-mode). Diff de falhas vs baseline provado vazio task a task.
- core: seed-0 = 780 falhas, SUBCONJUNTO ESTRITO das 811 do baseline f285c6e5. **Prova reproduzível** (o artefato `scratchpad/only-in-head.txt` era efêmero da sessão e não persiste; refazer assim): rodar `MIX_SEED=0 mix test` em `f285c6e5` e em `91adf826`, extrair as linhas de falha de cada, e `comm -13 <(sort baseline) <(sort head)` deve sair VAZIO (nenhuma falha nova no head) — foi o que se observou (0 bytes). A frente CORRIGIU 31 falhas do baseline (webhooks, uma casa com o dedup do I3); áreas da Onda I 60/0. ZERO regressão. (O "99" de handoffs anteriores era artefato de regex por alinhamento do ExUnit.)
- spb: 17542/76 = baseline seed-0 exato. ZERO regressão.
- Grep de unidade nos diffs: reais na borda, subcentavos internos — sem conversão errada.

## PACOTE DE DEPLOY HML (pré-condições + ordem)

### Migrations a aplicar via rpc (gotcha: sessão SSM corta com EOF cosmético, conferir schema_migrations)
- **pix (8)**: `20260704150000` unique return_id, `170000` camt060_requests, `190000` alcada_held, `230000` echo_probes, `235000` ownership_validations, `20260705000000` operational_alerts, `120000` daily_recon+snapshots, `130000` accounting_tables (com FKs).
- **spb (4)**: `20260704120000` mq_quarantine, `150000` hold columns, `20260705120000` accounting correlation unique, `130000` accounting reversal unique.
- **core (1)**: `20260705120000` spb_cosif_mirror.

### Pré-condições ANTES de migrar/deployar
1. **[S7e-FU2, P1] Checar duplicata RD/RC no SPB HML** antes da migração reversal-unique (senão o CREATE UNIQUE falha): `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`. Limpar se houver.
2. **[P11-FU1] Seed COSIF da cabine PIX** (chart_of_accounts com 0 contas em HML, conferido vivo): `bin/monetarie_pix rpc "SettlementService.Accounting.CosifSeeder.seed()"` — senão o fail-fast do P11 emite accounting_failure em cada evento.
3. **[S6] Seed dos R-legs 5.11 no SPB**: `bin/bacen_gateway rpc "BacenGateway.Release.seed_catalog_v511_r_legs()"` — R deve ir de 1.089 para 1.138. E `seed_cnt_roteiro` se o roteiro estiver vazio.
4. **[I1] Env do reconciliation SPB**: setar `SPB_CABIN_API_BASE_URL=http://spbapi-h.monetarie.internal` + `SPB_CABIN_ADMIN_LOGIN`/`PASSWORD` na task-def core-api, senão o SpbStatusReconciliation degrada (não crasha).
5. **Sidecar MQ**: rebuild da imagem (S4 mudou Sidecar.java; tag mutável homolog-latest, conferir digest).
6. **stopTimeout** já está 100 nas task-defs pix/core do deploy anterior.

### Flags — TODAS nascem OFF (ligar é decisão operacional)
`ALCADA_PIPELINE_ENABLED`, `ECHO_PROBE_ENABLED`, `DICT_BUDGET_MODE=advisory`, `DICT_VERIFY_ENFORCED`, `DICT_OWNERSHIP_ENFORCED`, `cosif_to_core` (system_config), `MED_ORPHAN_RECONCILER_MODE` (já void do deploy AVIV). Ordem do S7c: migração → deploy SPB → deploy Core → só então ligar cosif_to_core.

### Ordem de deploy
migrations (com pré-condição 1) → deploy pix-api + spb-api + core-api (imagens novas) → seeds (pré-condições 2/3) → env I1 (pré-condição 4) → sidecar (5) → validação viva T1-T5.

## DEPLOY EXECUTADO EM HML (2026-07-05) — completo e validado vivo

`origin/main = 91adf826` (a frente inteira, 54 commits, está em `main`). Imagens arm64 com tag imutável `homolog-91adf826-paridade-20260705` (spb digest `sha256:300ea04a…`). Rollout na ordem S7c (SPB → PIX → Core), todos `COMPLETED`, 1/1, deployment único, sem crash-loop:
- **spb-api:31** (spb-api img nova + sidecar MQ apontado para a tag IMUTÁVEL, 61 env + 7 secrets preservados)
- **pix-api:116** (img nova)
- **core-api:84** (img nova + I1 `SPB_CABIN_API_BASE_URL=http://spbapi-h.monetarie.internal`, `SPB_CABIN_ADMIN_LOGIN=admin@monetarie.com.br` como env, `SPB_CABIN_ADMIN_PASSWORD` como secret → `monetarie/homolog/admin/spb/initial_password`; flags AVIV do :83 preservadas)

**Migrations (13, via rpc no nó vivo — eval não herda secret):** SPB 4 = `[20260704120000, 20260704150000, 20260705120000, 20260705130000]` (pré-condição RD/RC moot: `accounting_entries` 0 linhas). PIX 8 aplicadas; **gotcha real:** a #5 `20260704235000` (bare `CREATE TABLE ownership_validations`) abortava com `42P07 duplicate_table` — **fato observado:** a tabela `monetarie_dict.ownership_validations` já existia e estava VAZIA (0 linhas, confirmado por `SELECT count(1)`; causa provável = resíduo de tentativa parcial anterior, não provado). Resolvido com `DROP TABLE ... CASCADE` da tabela vazia + re-run, aplicando `[20260704235000, 20260705000000, 20260705120000, 20260705130000]`. Core 1 = `20260705120000` (spb_cosif_mirror), confirmado em schema_migrations.

**Seeds:** PIX COSIF `inserted: 2` (as 2 contas default de settlement; fail-fast P11 satisfeito → `chart_of_accounts=2`, zero alertas `cosif`); SPB R-legs `%{config: 49, tag_definitions: 9}` → **R 1089 → 1138**; roteiro SPB semeado do zero `%{roteiro: 303, contas: 714, tags: 354}`.

**Validação viva (evidência):** 7 health 200 via VPN (coreapi/pixapi/spbapi/coreadmin/pixadmin/spbadmin/ib). **I1 provado ao vivo:** `CabinStatusLookup.configured? = true`, secret injetado, `by_transaction_id("probe…")` = `{:error, :not_found}` (autenticou no cabine SPB e consultou — falha de auth teria retornado erro de auth, não not_found). Tabelas novas queryáveis (daily_reconciliations/daily_balance_snapshots/camt060_requests/operational_alerts). **SPB grid `direction_flag='R'` = 1138 vivo; `spb_cnt_roteiro` = 303.** Único `operational_alert` aberto = `stuck_outbound` ("Mensagens outbound presas") — **é o P8 funcionando**: o alerta prova que há outbound preso; a causa (HSM RTM fora do ar) é **corroborada** pela queda conhecida do HSM RTM em 2026-07-03 ~20:22 (registrada em handoffs/MEMORY.md), não afirmada só pelo alerta. Não é defeito de deploy.

**Flags:** todas as da paridade nascem OFF e permaneceram OFF (`ALCADA_PIPELINE_ENABLED`, `ECHO_PROBE_ENABLED`, `DICT_VERIFY_ENFORCED`, `DICT_OWNERSHIP_ENFORCED`, `cosif_to_core`); `MED_ORPHAN_RECONCILER_MODE=void` e flags AVIV seguem do deploy anterior. Ligar é decisão operacional.

**Como reverificar cada fato vivo (comandos, sem confiar na prosa):**
- Revisões vivas: `awsmon ecs describe-services --cluster monetarie-greenfield-homolog --services spb-api pix-api core-api --query 'services[].{svc:serviceName,td:taskDefinition,rollout:deployments[0].rolloutState,run:runningCount}'` → td :31 / :116 / :84, COMPLETED, 1.
- Imagem/digest: `awsmon ecr describe-images --repository-name monetarie/spb-api --image-ids imageTag=homolog-91adf826-paridade-20260705 --query 'imageDetails[0].imageDigest'` → `sha256:300ea04a…`.
- Secret I1 é referência (não plaintext): `awsmon ecs describe-task-definition --task-definition monetarie-core-api-homolog:84 --query 'taskDefinition.containerDefinitions[0].secrets[?name==\`SPB_CABIN_ADMIN_PASSWORD\`]'` → `valueFrom` = ARN.
- Migrations: rpc no nó vivo `SELECT version FROM schema_migrations WHERE version >= 20260704120000` (PIX Shared.Repo 8, SPB BacenGateway.Repo 4, Core Monetarie.Infra.Repo.Base 1).
- Seeds: `SELECT count(1) FROM monetarie_settlement.chart_of_accounts` = 2; `SELECT count(1) FROM message_type_config WHERE direction_flag='R'` = 1138; `SELECT count(1) FROM spb_cnt_roteiro` = 303.
- I1: rpc `Monetarie.Services.SpbIntegration.CabinStatusLookup.by_transaction_id("probe")` = `{:error, :not_found}` (só alcançável APÓS login OK; auth falha retorna `{:auth_failed, status}`).
- Health: `for h in coreapi-h pixapi-h spbapi-h coreadmin-h pixadmin-h spbadmin-h ib-h; do curl -s -o /dev/null -w "$h=%{http_code}\n" http://$h.monetarie.internal/health; done` → 200.
- (Todos os itens acima foram reconfirmados de forma independente por um workflow de auditoria de 6 dimensões no fechamento 07-05: git/ECS/DB/health/secret-safety/doc-truth + crítico adversarial.)

**Prova de que as 13 migrations foram NÃO-DESTRUTIVAS (spot-check pós-deploy 07-05):** o ledger de dinheiro do Core `public.account_entries` = **231.412 linhas, idêntico ao âncora da re-migração de 07-01** (nada apagado nem movido). `accounts` 1606→1607 e `users` 1542→1543 (+1 cada) são onboarding normal entre 07-01 e 07-05, não efeito das migrations (que são schema-only e não escrevem nessas tabelas; migration destrutiva faria a contagem CAIR e mudaria `account_entries`). Saldo de cliente inalterado (deriva do `account_entries`, que não mudou).

**Riscos abertos / rollback (declarados, não escondidos):**
- **Fluxo de dinheiro NÃO validado ao vivo:** T1-T3 (inbound real) e o ENVIO PIX/SPB dependem de tráfego BACEN homolog e do HSM RTM voltar. "Zero regressão" está provada por diff de conjunto de falhas com seed fixo, NÃO por liquidação financeira viva. O alerta `stuck_outbound` mostra que o ENVIO está fail-closed (nada some, fica "Pendente"). Não fabricar mensagens em HML só para "fechar" T1-T5.
- **Flags da paridade shipadas OFF (dark):** o código novo está no ar mas não exercitado; ligar QUALQUER flag depois é uma mudança de estado que exige validação própria.
- **Rollback:** task-defs anteriores ACTIVE e lançáveis — `core-api:83`, `pix-api:115`, `spb-api:30` (verificado 07-05). As migrations são aditivas/`IF NOT EXISTS`; um rollback de imagem não requer rollback de schema.

## Validação viva T1-T5 (roteiro; HSM RTM fora bloqueia só o ENVIO PIX)
- T1 entrada PIX creditando + dedup; T2 rejeição visível sem vazamento (reason_code no metadata, saldo PI sem sobra); T3 crédito SPB idempotente por num_ctrl_str; T4 PixStatusReconciliation pelo caminho API (não SQL — I2); T5 TbPgReconciliation. Extras desta frente: correlação camt.060 (P3) e tracking pacs.004 (P2) quando o BACEN homolog cooperar; o envio PIX real segue bloqueado pelo HSM RTM.

## Nota latente — RETRATADA na auditoria de fechamento (07-05)
Uma versão anterior deste handoff afirmava haver `fc.institution_id == nil` numa query do "fee do SPB Handler" (que deveria ser `is_nil/1`). **A auditoria de fechamento 07-05 não encontrou esse trecho em lugar nenhum do SPB** (`grep` em `spb/services/bacen_gateway/lib/` = zero; não existe `fc.` no SPB). O código real de fee com `institution_id` está no **Core** e está CORRETO: `fc.institution_id == ^institution_id` (`core/backend/lib/monetarie/workers/fees/monthly_batch_worker.ex:136`, `use_cases/fees.ex:238`). Portanto a nota era um snippet fantasma e fica retratada; não há follow-up de `is_nil` pendente.

## Follow-ups residuais (todos no plano, nenhum de dinheiro aberto)
P9a métricas auto-PIX; I2/I3 higiene (DICT_SOURCE_DATABASE_URL ociosa, NPC/STA consumers); P5/P6 observabilidade; S2-FU (rescue do store_closing_balance); S5-FU (schema out-of-band do PendingQueue); S6-FU (65 legs base ausentes); S7e-FU (unique cd_conta); pendências de dono do S7 (arquivo COSIF posicional, drop de tabelas congeladas, remoção do módulo COSIFEngine).
