# Handoff — ✅ FIX da colisão de identidade das ordens SISBAJUD (índice único por tipo)

**Data:** 2026-07-09
**Status:** CORRIGIDO e provado (TDD fim-a-fim na ingestão: 8 testes novos; regressão 414/0). Local, sem push.
**Branch:** `feat/monetarie_improviments`.

---

## 1. Os bugs (eram TRÊS, não um)

1. **Colisão de índice único**: `judicial_orders_protocolo_seq_reit_idx` era único em `(protocolo, sequencial_bloqueio, reiteracao_bloqueio)` SEM distinguir `order_type`. Pelo leiaute v1.11 §2.4, UNBLOCK/CANCEL/TRANSFER referenciam o BLOCK original pelo mesmo trio → a ingestão de **qualquer ordem pós-bloqueio** colidia com o próprio BLOCK e estourava `Ecto.ConstraintError` (sem `unique_constraint` no changeset = exceção, ingest morto).
2. **BLOCK invisível**: `process_block` gravava a referência só em `bcb_protocol` + metadata — as colunas `protocolo/sequencial_bloqueio` ficavam NULL. O `find_block_order_by_reference` busca pelas colunas → **nenhum desbloqueio/cancelamento/transferência achava o bloqueio** ingerido por arquivo.
3. **TRANSFER nunca criava ordem**: o registro 07 não tem CPF do réu/vara (são do bloqueio referenciado); `document_number`/`court_code` ficavam vazios e o changeset **sempre** rejeitava a ordem de transferência vinda de arquivo.

## 2. O fix

**Identidade única POR TIPO** (fiel ao leiaute), com índices únicos parciais:

| Tipo | Chave única | Índice |
|---|---|---|
| BLOCK | (protocolo, seq_bloqueio, reit_bloqueio) | `judicial_orders_block_identity_idx` |
| CANCEL | (protocolo, seq_bloqueio, reit_bloqueio) | `judicial_orders_cancel_identity_idx` |
| UNBLOCK | trio de referência + (seq_desbloqueio, reit_desbloqueio) | `judicial_orders_unblock_identity_idx` |
| TRANSFER | trio de referência + (seq_transferencia, reit_transferencia) | `judicial_orders_transfer_identity_idx` |

UNBLOCK/TRANSFER incluem o trio de referência na chave porque o leiaute não garante que o sequencial próprio seja numerado por protocolo (pode reiniciar por bloqueio) — dois desbloqueios legítimos de bloqueios distintos do mesmo protocolo não podem colidir (achado D2 da revisão adversarial).

**Mudanças:**
- `priv/repo/migrations/20260709220000_judicial_order_identity_per_type.exs`: 4 colunas novas (`sequencial/reiteracao_desbloqueio`, `sequencial/reiteracao_transferencia`), drop do índice antigo **ANTES** dos backfills (D1: um CANCEL/UNBLOCK pré-existente ocupando o trio faria o backfill do BLOCK explodir com o índice antigo vivo), backfills com guarda `~ '^[0-9]+$'` (D6: metadata legado pode ter lixo) cobrindo as 2 formas de linha legada (D7), e os 4 índices novos.
- `schemas/judicial/judicial_order.ex`: 4 campos novos + cast + `unique_constraint` nomeado para cada índice (colisão vira `{:error, changeset}` gracioso).
- `regulatory/sisbajud/file_processor.ex`:
  - `process_block` persiste `protocolo/sequencial_bloqueio/reiteracao_bloqueio` nas colunas (com `|| 0` — D5: o lookup coage nil→0, a coluna precisa casar);
  - `process_transfer` espelha o `process_unblock`: resolve o BLOCK referenciado e **herda** document_number/court_code/cpf_cnpj_reu; sem prior → responde `block_found=false` ("00") sem criar ordem;
  - UNBLOCK/TRANSFER persistem a identidade própria nas colunas novas;
  - **Reapresentação** (D3/D4): colisão de identidade no `create_order` agora é tratada como reapresentação do BCB (que reenvia a MESMA ordem em remessa nova quando não recebeu nossa resposta — o guard por hash de arquivo não pega). Se a ordem original está em estado terminal (`executed/partial/no_balance/not_found/responded/cancelled`), **re-emite a resposta original** (mesmo código/valores + campos de correlação; CANCEL sempre 01 per Reg. §4.10). Em estado não-terminal mantém a linha de erro genérica — reprocessar automaticamente poderia duplicar movimento de dinheiro; reprocesso manual via `POST /sisbajud/orders/:id/process`.

## 3. Testes (TDD, RED→GREEN)

`test/monetarie/use_cases/regulatory/sisbajud/order_identity_ingest_test.exs` — **fim-a-fim na ingestão** (constrói as linhas posicionais reais dos registros 03/05/07 e passa pelo `FileProcessor.process/2` — exatamente o caminho que os testes de struct nunca exercitaram):
1. Desbloqueio não colide com o bloqueio (RED: `ConstraintError`)
2. Dois desbloqueios parciais do mesmo bloqueio coexistem
3. Re-ingestão do mesmo desbloqueio: graciosa, sem duplicar, resposta reapresentada com código 01 + `sequencial_desbloqueio` correto
4. Desbloqueios de bloqueios DIFERENTES do mesmo protocolo com mesmo sequencial próprio coexistem (D2)
5. Cancelamento não colide com o bloqueio
6. Cancelamento reapresentado responde **01** (Reg. §4.10), não o "00" genérico (D3)
7. Transferência não colide (e prova o fix do document_number herdado)
8. Re-ingestão do mesmo bloqueio: graciosa, sem duplicar

Regressão: judicial + sisbajud + blocked_breakdown = **414 testes, 0 falhas**; integração TB do fix de unidade (2) verde.

## 4. Revisão adversarial (7 vetores + 7 achados, todos tratados ou documentados)

- **D1 (deploy-blocker)**: ordem drop-índice-antigo vs backfill → corrigido (drop antes).
- **D2 (dinheiro)**: identidade UNBLOCK/TRANSFER sem o trio de referência colidiria desbloqueios de bloqueios distintos → chave composta de 5 colunas.
- **D3 (semântica de fio)**: duplicata respondia "00" genérico sem campos de correlação → reapresentação re-emite a resposta original.
- **D4 (comportamento)**: ordem não-terminal ocupa a identidade; reprocesso automático seria risco de duplo movimento → mantido erro genérico + reprocesso manual, documentado no código. **Nota ops**: CANCEL órfão (arquivos fora de ordem) ocupa a identidade do cancel; se o BLOCK chegar depois e o BCB reapresentar o cancel, precisa de reprocesso manual.
- **D5**: `sequencial_bloqueio` sem `|| 0` deixava linha fora do índice e invisível ao lookup → `|| 0` nos 4 pontos de ingest.
- **D6**: `::int` sem guarda podia explodir com metadata legado → regex `^[0-9]+$`.
- **D7**: linhas legadas com `protocolo` populado mas `sequencial_bloqueio` NULL ficavam invisíveis → backfill cobre as 2 formas.

## 5. Estado dos bancos locais

- `mon_core_test` e `mon_core` (dev): migration aplicada (o `mon_core` via SQL direto + `schema_migrations`, pois o `Release.migrate` desse banco quebra numa migration antiga do ETL) + delta dos índices de 5 colunas. Backfill no `mon_core` corrigiu as 2 ordens BLOCK legadas (agora com `protocolo` na coluna).
- Imagem `monetarie-core-api:local` rebuildada com o código novo (container `core-api` recriado).

## 6. Follow-ups

- CANCEL reiterado com bloqueio AINDA não ingerido (órfão) → reprocesso manual (nota D4).
- `Formatter.encode_latin1` UTF-8 no builder do 5302 (pendência antiga).
- Pré-condição de deploy do fix de unidade 100x (handoff de 2026-07-09) continua valendo para HML/prod.

## 7. Ponteiros

- Handoffs anteriores: `2026-07-09-sisbajud-fix-unidade-bloqueio-100x.md` (fix de unidade), `2026-07-08-sisbajud-bug-unidade-bloqueio-100x.md` (diagnóstico).
- Memória: `sisbajud-leiautes-e-fatos-locais`, `pendentes-sisbajud-pos-fix`.
