# Incidente TED em HML — STR0008R1 confirmado classificado como erro (SitLancSTR string vs integer)

**Data:** 2026-07-23
**Ambiente do incidente:** Homologação (HML) — `spb-api`
**Severidade:** Alta (money-path — TED confirmada pelo BACEN é falsamente rejeitada ao Core)
**Status:** Causa raiz confirmada empiricamente + fix aplicado e validado (TDD 905/0). **Sem commit e sem deploy** (aguardando decisão).

---

## 1. Sintoma

Uma TED enviada pelo HML (mensagem `STR0008`) aparecia com **"Erro"** na linha do tempo do ciclo de vida, apesar de o BACEN ter **confirmado** o lançamento.

- Operação: `0c337f9e-eb59-4d83-8f74-c8d01a49d24f`
- `NumCtrlIF`: `TRF20260723fdd9f5547`
- `NumCtrlSTR`: `STR20260723033051743`
- Resposta do BACEN: `STR0008R1` com **`SitLancSTR = 1`** (Efetivado), `signature_valid: true`
- Diagnóstico interno gravado: `response_code = "SIT_1"`, `status_code = 9` (`RSPER` / `r1_error`)

Linha do tempo (BRT): `EMQS`/`EMSG` 10:24:12 → `RSPER` (Erro) 10:24:16.

---

## 2. Causa raiz

Defeito **duplo** — cada metade, isolada, já bastaria para produzir o erro dado o estado do outro:

### 2.1. Bug de tipo (código) — `lifecycle_engine.ex :: classify_sit_lanc_str/3`
- O parser `messages/str/str0008r1.ex:55` extrai `SitLancSTR` via `MessageParser.get_text/2` → **STRING** (`"1"`).
- `classify_sit_lanc_str` passava essa string direto para `StatusDePara.map_status("STR", "1")`.
- A cláusula built-in autoritativa `map_status("STR", valor)` (`str/status_de_para.ex:207`) tem guard **`when is_integer(valor)`** → **não casa com string**.
- Resultado: o de‑para built-in `@str_sit_lanc_mapping` (`1 => {4, true, true, true}` = Efetivado/confirmado) **nunca era consultado**; a execução caía na cláusula genérica, que depende das tabelas do banco.
- Os handlers de STR já convertiam corretamente (`str_handler.ex:279` usa `Integer.parse` antes do `map_status`). O `classify_sit_lanc_str` era a **única** exceção — por isso o defeito passou despercebido (todos os testes passavam `sit` como integer).

### 2.2. Dado ausente (operacional, HML) — `group_status_map` vazio
- A cláusula genérica consulta `group_status_map`, que em **HML tem 0 linhas** (a seed `044_runtime_readiness_seed.exs` nunca foi aplicada ali).
- Sem built-in (2.1) e sem tabela (2.2) → `{:error, :unmapped_status}` → catch-all → `{"r1_error", 9, "SIT_1"}`.

### Consequência (money-path)
`RSPER`/`r1_error` faz o `CoreNotifier` enviar **"rejected" ao Core** (log HML `13:24:16 [CoreNotifier] Enqueued rejected`), liberando o hold / devolvendo o valor ao pagador — quando a TED foi **efetivada** no BACEN.

---

## 3. Evidências vivas (AWS read-only, conta `990933657879`, sa-east-1)

### 3.1. Logs HML `/ecs/monetarie/homolog/spb-api`
```
13:24:11 [CoreEventConsumer] STR0008 TED request: TRF20260723fdd9f5547
13:24:12 [CoreEventConsumer] Enqueued status accepted to Core for TRF20260723fdd9f5547
13:24:16 [CoreNotifier]       Enqueued rejected to Core for TRF20260723fdd9f5547
13:24:16 [warning] Lifecycle: R1 SitLancSTR=1 (família STR) sem disposição terminal
         para operação 0c337f9e-... — marcado r1_error SIT_1
...
[StatusDePara] Loaded from DB: 0 standard, 0 special rules
```

### 3.2. Reprodução ao vivo (rpc read-only no `spb-api` HML)
```
map_status("STR", "1")  [STRING]  → {:error, :unmapped_status}     ← falha
map_status("STR",  1 )  [INT]     → {:ok, {4, true, true, true}}   ← funcionaria
classify_r1(…"STR0008R1", "1")    → {"r1_error", 9, "SIT_1"}       ← reproduz o erro exato
SELECT count(*) FROM group_status_map → 0                          ← HML vazio
```
A única diferença entre falhar e funcionar é **string vs integer**.

### 3.3. PRD **não** está com incidente ativo (rpc read-only no `spb-api` PRD)
```
map_status("STR", "1")  [STRING]  → {:ok, {4, true, true, true}}   ← resolve
SELECT count(*) FROM group_status_map → 495                        ← populado
```
Em PRD o `group_status_map` cobre o buraco de tipo (a genérica resolve mesmo com string). **Porém o bug de tipo continua latente lá**: o built-in de segurança está inacessível; se o `group_status_map` de PRD for perdido/re-seeded ou uma task subir antes da seed, PRD quebra igual ao HML.

---

## 4. Fix aplicado

`spb/services/bacen_gateway/lib/bacen_gateway/lifecycle_engine.ex` (`classify_sit_lanc_str/3`):

1. Converte `sit` (string do parser) para **integer** antes de `map_status` — espelhando o padrão já usado em `str_handler.ex`. Isso restaura o de‑para built-in autoritativo como **rede de segurança independente do banco**.
2. Generaliza a cláusula de rejeição de `{9, false, …}` para `{status, false, …} when status in [9, 999]`, cobrindo as **duas** representações do mesmo estado terminal (built-in STR usa `999`; `group_status_map` e built-in LPI usam `9`). Pendente (`13`/`3`) **não** casa → segue como `r1_error` (aguarda R2), preservando o comportamento.

Trecho:
```elixir
sit_int =
  case Integer.parse(String.trim(to_string(sit))) do
    {n, _} -> n
    :error -> -1
  end

case BacenGateway.STR.StatusDePara.map_status(family, sit_int) do
  {:ok, {_status, true, _tariffed, _finalizer}} ->
    {"r1_confirmed", 7, "ACCP"}

  {:ok, {status, false, _tariffed, _finalizer}} when status in [9, 999] ->
    {"r1_rejected", 8, "RJCT"}

  _ ->
    Logger.warning(...); {"r1_error", 9, "SIT_#{sit}"}
end
```

---

## 5. Validação (TDD)

Teste novo: `test/bacen_gateway/lifecycle_str0008r1_test.exs` (setup deixa o `group_status_map` **vazio**, replicando o HML — se passa, é porque o built-in foi consultado).

| Caso | Antes (RED) | Depois (GREEN) |
|---|---|---|
| STR `SitLancSTR="1"` (Efetivado) | `{"r1_error", 9, "SIT_1"}` ❌ | `{"r1_confirmed", 7, "ACCP"}` ✅ |
| STR `"5"/"9"` (Rejeitado) | `r1_error` | `r1_rejected` ✅ |
| STR `"14"` (Cancelado) | `r1_error` | `r1_rejected` ✅ |
| STR `"17"` (Pendente) | `r1_error` | `r1_error` (preservado) |
| LPI `"1"/"5"` | — | sem regressão ✅ |

- **Paridade com PRD confirmada** (mesmo desfecho com `group_status_map` populado).
- Suítes: `lifecycle_str0008r1_test` **2/0**; regressão `str/` + `messages/str/` + LPI = **905/0**.

---

## 6. Validação contra a doc oficial do BACEN (Catálogo de Serviços do SFN v5.12)

Fonte: **Catálogo de Serviços do SFN — Volume I (STR), versão 5.12** (BACEN/Deinf, 27/03/2026).

### 6.1. A `STR0008R1` é resposta de situação, não erro (pág. 266)
- **STR0008** — "IF requisita Transferência entre contas de clientes" (a TED). *(pág. 252-253, 266)*
- **STR0008R1** — "Resposta ao Requisitante", **Emissor: STR → IF‑DEBITADA**, com campo obrigatório **`<SitLancSTR>` "Situação Lançamento STR" `[1..1]`**. *(pág. 266)*
- **STR0008E** — Mensagem de **Erro** é categoria *separada* (XSD `priv/xsd/v512/STR/STR0008E.XSD` no repo; "Mensagem de Erro – enviada em retorno… quando houver erro").

→ Um erro chegaria como `STR0008E` (com `TagErro`). O que chegou foi `STR0008R1` + `SitLancSTR=1` + assinatura válida = **confirmação legítima**. Classificar como `r1_error` era incorreto.

### 6.2. Estados oficiais do lançamento no STR (introdução do grupo, pág. 252, literal)
> "antes de efetivar o débito, o STR verificará se há saldo suficiente na conta para suportá-lo. Não havendo saldo suficiente, a operação será **rejeitada** ou inserida na **fila de pendências** de acordo com a natureza da operação"

Mais **STR0011 – "Cancelamento de lançamento STR pendente"**. As quatro disposições — **Efetivado / Rejeitado / Pendente / Cancelado** — são exatamente as que o fix trata.

### 6.3. Conformidade de cada ramo do fix

| `SitLancSTR` | Estado oficial (Catálogo SFN v5.12) | Fix classifica | Conforme |
|---|---|---|---|
| 1–4 | **Efetivado** (débito efetivado) | `r1_confirmed` | ✅ |
| 5,6 | **Rejeitado** por insuficiência de saldo | `r1_rejected` | ✅ |
| 8,9 | **Rejeitado** por outras razões | `r1_rejected` | ✅ |
| 14,15 | **Cancelado** (STR0011) | `r1_rejected` | ✅ |
| 17–20 | **Pendente** (fila de pendências) | `r1_error` → aguarda R2 (não terminal) | ✅ |
| 22–25 | Pendência rejeitada | `r1_rejected` | ✅ |

**Nota de fonte (honestidade):** os *volumes de mensagens* do Catálogo confirmam a **estrutura e os estados**, mas **não trazem a tabela numérica** (1=…, 5=…). A numeração exata vive no documento de *Domínios de Dados* e está materializada no repo em duas fontes convergentes que homologam contra o BACEN: o de‑para built-in `str/status_de_para.ex` (documentado como "authoritative BCB codes": `1-4 Efetivado; 5,6 Rej. insuf. saldo; 8,9 Rej. outras razões; 14,15 Cancelado; 17-20 Pendente; 22-25 Pendência rejeitada") e `priv/repo/seeds/bacen_reference_data/group_status_map.csv.gz` (importado do legado EvolutionPro), ambos com **`STR|1 → confirmado`**. O fix está alinhado com as três.

---

## 7. Impacto e paridade

- **HML:** TED funcionalmente quebrada (toda `STR0008` confirmada era falsamente rejeitada). Corrigido pelo fix.
- **PRD:** operacional hoje (`group_status_map` = 495 linhas); o fix elimina a fragilidade latente (rede de segurança do código volta a funcionar).
- Mudança benigna de comportamento vs. lógica antiga: **cancelamento** (`SitLancSTR` 14/15) passa de `r1_error` para `r1_rejected` — mais preciso (cancelamento é rejeição terminal; mesmo efeito de money-path: libera o hold).

---

## 8. Pendências / próximos passos

1. **Deploy do `spb-api`** com o fix — é o que faz o HML remoto parar de errar (o código lá ainda é o antigo).
   - Mitigação alternativa imediata (sem deploy): aplicar a seed `044_runtime_readiness_seed.exs` no HML para popular o `group_status_map` como em PRD. O fix de código é a solução robusta e cobre a fragilidade latente de PRD.
2. **Commit** (2 arquivos abaixo) — segue retido até autorização.
3. (Opcional) Regressão completa do `bacen_gateway` além do escopo STR.

### Arquivos alterados (sem commit)
- `spb/services/bacen_gateway/lib/bacen_gateway/lifecycle_engine.ex` — fix em `classify_sit_lanc_str/3`
- `spb/services/bacen_gateway/test/bacen_gateway/lifecycle_str0008r1_test.exs` — teste de regressão (RED→GREEN)

---

## Fontes
- Catálogo de Serviços do SFN — Volume I (STR) v5.12: `https://www.bcb.gov.br/content/estabilidadefinanceira/cedsfn/Catalogos/Catalogo_de_Servicos_do_SFN_Volume_I_Versao_512.pdf` (págs. 252-253, 266)
- Página oficial do Catálogo (CEDSFN): `https://www.bcb.gov.br/estabilidadefinanceira/cedsfn`
- Repo (fontes do domínio): `spb/services/bacen_gateway/lib/bacen_gateway/str/status_de_para.ex`; `spb/services/bacen_gateway/priv/repo/seeds/bacen_reference_data/group_status_map.csv.gz`; `spb/services/bacen_gateway/priv/xsd/v512/STR/`
