# Dossiê de Paridade — admi.002 / admi.004 / admi.005 / admi.006 / admi.007 (mensagens de controle SPI)

Auditor: sistemas PIX (read-only, prova arquivo:linha e tabela.coluna, zero inferência).
Data: 2026-07-23. Escopo: mensagens administrativas/controle do SPI (família admi.*).

---

## 0. Sumário executivo

- **admi.005 / admi.006 / admi.007 NÃO EXISTEM em nenhum dos dois lados.** O legado
  não tem enum, handler, tabela nem catálogo para elas; a nossa cabine as removeu
  explicitamente como "fictícias" por não constarem nos XSDs oficiais v5.12 do SPI.
  **Paridade total (coberto)** — não há avisos operacionais/janela admi.005/006/007
  no catálogo real do SPI. As "consultas de janela/parâmetros/manutenção" (endpoints
  `get_operational_schedule`/`get_system_parameters`/`get_maintenance_windows`) eram
  invenções nossas, já eliminadas.
- **admi.002 (MessageReject)** é **bidirecional** no legado:
  - INBOUND (BACEN → nós): o legado só reage ao caso **camt.060 "não existe
    lançamento"** e, por correlação, **rejeita a operação original (pacs.008/pacs.004)**.
  - OUTBOUND (nós → BACEN): o legado **envia admi.002** para recusar uma mensagem
    recebida que falhou na validação XSD (config de catálogo `CdMsgErro = ADMI.002`
    para praticamente todos os tipos).
  - **Nosso lado**: o tratamento INBOUND de admi.002 é **mais amplo** (correlação
    DIRETA por `RltdRef/Ref`, correlação REDA e correlação camt.060). Mas o lado
    OUTBOUND diverge: **nós nunca enviamos admi.002 em produção** (o `build_admi002`
    é só do simulador) e, na falha de validação/assinatura de mensagem recebida,
    respondemos **pacs.002 RJCT**, não admi.002.
- **admi.004 (SystemEventNotification / aviso operacional)**: os dois ingerem. O
  legado **estrutura** o aviso numa tabela dedicada `SpiADMI004` (EvtCd/EvtDesc/DtHrAviso,
  upsert dedup) e **exibe na tela** (tradutor `TraduzADMI004`). Nós só gravamos o XML
  cru em `bacen_inbound` (auditoria), **sem extrair EvtCd/EvtDesc, sem registro
  consultável e sem exibição em tela** (o front Vue não tem nenhuma referência a admi).

---

## 1. Comportamento do LEGADO (com prova)

### 1.1 Catálogo de mensagens (fonte da verdade legada)

`CRK_SPIDOMINIO.dbo.SpiCadMessage` (colunas `CdMsg, FlFinanceira, FlAssinar, CdMsgResposta, CdMsgErro, Classificacao, Caracteristica`):

| CdMsg | CdMsgResposta | CdMsgErro | Caracteristica | FlFinanceira |
|---|---|---|---|---|
| ADMI.002 | - | - | 5 (Erro) | 0 |
| ADMI.004 | - | ADMI.002 | 8 (Aviso) | 0 |
| PACS.008 | PACS.002 | **ADMI.002** | 1 (Pagamento) | 1 |
| PACS.004 | PACS.002 | **ADMI.002** | 6 (Devolucao) | 1 |
| CAMT.060 | - | **ADMI.002** | 4 | 0 |
| CAMT.054 | - | **ADMI.002** | 3 | 0 |
| REDA.014 | REDA.016 | **ADMI.002** | 7 | 0 |
| (todas as demais) | ... | **ADMI.002** | ... | ... |

Prova: query viva `CRK_SPIDOMINIO.dbo.SpiCadMessage`.
Enum `enumCaracteristicaMensagem` (SPI.Core.General.decompiled.cs:7724-7746): `5 = Erro`,
`8 = Aviso`. **`CdMsgErro = ADMI.002` para quase todo tipo recebido** = quando uma
mensagem recebida falha validação de camada de mensagem, a resposta de erro é admi.002.

`CRK_SPIDOMINIO.dbo.SpiCadMessageVigencia` (versões vigentes):
- ADMI.002: `admi.002.spi.1.1` (2020) … `admi.002.spi.1.5` (2025-08-04). Config
  `LeitorE2EId`: `TagRepet=*/Document/admi.002.001.01`, `TagEndToEndId=./RltdRef/Ref`,
  `TagAddData=./Rsn/AddtlData`; `ClasseEntrada=SPI.Core.General.DTO.ADMI002FullDTO`.
- ADMI.004: `admi.004.spi.1.0` (2020-06-18) … `admi.004.spi.1.2` (2025-08-04);
  processamento NULL (só aviso).
- **admi.005/006/007: ZERO linhas** (query viva confirmou apenas ADMI.002 e ADMI.004).

Enum `enumTipoMsg` (SPI.Core.General.decompiled.cs:8095-8110): só `ADMI002` e `ADMI004`.
Grep de `admi.00[567]` em todos os `.decompiled.cs`: **nenhum resultado**.

### 1.2 admi.002 INBOUND (BACEN → legado) — recusa correlacionada à camt.060

Dispatcher de mensagens recebidas — `SPI.Core.Worker.TratamentoRetornoApiBacen.Infrastructure.decompiled.cs:1005-1035`:
a cadeia `else if` só roteia **ADMI.002** para `ImportaADMI002`; **admi.004 NÃO está na
cadeia de recepção** (entra pelo caminho de "Avisos", ver 1.4).

`ImportaADMI002` (linhas 1115-1133):
```
if (chaveMensagem != null
    && chaveMensagem.CdMsg == "CAMT.060"
    && UtilsXml.TagValue(objConverter.XmlMsg, "RsnDesc")
       == "Não existe lançamento com o identificador da operação solicitado")
{ await _tratamentoCredito.RejeitaPgtoPorADMI002(chaveMensagem); }
```
→ **Só age quando** a mensagem impactada (resolvida por E2E) é uma **CAMT.060** E o
`Rsn/RsnDesc` é exatamente a string "Não existe lançamento com o identificador da
operação solicitado" (match literal).

`RejeitaPgtoPorADMI002` (linhas 2079-2106):
- lê `MensagensFull.JsonInput` da operação original → `CAMT060ApiDetalhaLanctoDTO.IdEventoPesquisa`;
- se começa com **"E"** → localiza a **PACS.008** por `EndToEndId`; senão → **PACS.004** por `RtrId`;
- se a mensagem localizada **não** está em `{Efetivada, Rejeitada, PendenteAprovacao,
  PendenteAprovacaoRestanteLote}` → **`IdStatus = Rejeitada`** e persiste.

Ou seja: consulta proativa de operação (camt.060 detalha-lançamento) que volta "não
existe" → **rejeita automaticamente a operação original** (transição de status money-path).

Tradução na tela (Angular) — `SPI.Core.Angular.Application.decompiled.cs:728-741`,
`TraduzADMI002` extrai: `RltdRef/Ref` (REFERÊNCIA), `Rsn/RjctgPtyRsn` (MOTIVO ERRO),
`Rsn/RjctnDtTm` (DATA ERRO), `Rsn/ErrLctn` (LOCALIZAÇÃO ERRO), `Rsn/RsnDesc` (DESCRIÇÃO
ERRO), `Rsn/AddtlData` (DADOS ADICIONAIS ERRO). Raiz: `Envelope/Document/admi.002.001.01`.

### 1.3 admi.002 OUTBOUND (legado → BACEN) — recusa de mensagem recebida inválida

`SPI.Core.Mensageria.Geral.Application.decompiled.cs:262-282` — `ConvertToADMI002FullDTO`
monta um `ADMI002FullDTO` a partir de uma mensagem recebida que falhou validação:
`Erros = msg.ErrosValidacao`, `MessageIdOrig = msg.Header.BizMsgIdr` (ou `PIResourceId`),
`IspbCreditor = enumISPBPadrao.Bacen`, `CdMsg = msg.CdMsgGerar`, `MsgDefIdr = msg.MsgDefIdrGerar`
(vindos do `CdMsgErro=ADMI.002` do catálogo).

Builder do XML — `SPI.Core.Mensageria.Application.Book.v111.decompiled.cs:6315-6362`
(`MontaMsg.Impl.ADMI002`): emite `<admi.002.001.01>` com
`RltdRef/Ref = MessageIdOrig`, `Rsn/RjctgPtyRsn = XSDValidationError.RjctgPtyRsn`,
`Rsn/RjctnDtTm`, `Rsn/RsnDesc = XSDValidationError.RsnDesc`, dentro de `Envelope/AppHdr/Document`.

→ **O legado ENVIA admi.002 ao BACEN** para recusar, na camada de mensagem, qualquer
mensagem recebida que reprove no XSD (RjctgPtyRsn/RsnDesc = erros de validação).

### 1.4 admi.004 INBOUND (BACEN → legado) — aviso operacional persistido e exibido

Classificação: `SPI.Core.Mensageria.Application.decompiled.cs:125` marca
`MsgAviso = (Caracteristica == enumCaracteristicaMensagem.Aviso)` — admi.004 tem
Caracteristica 8 (Aviso). Conversão: `SPI.Core.Application.decompiled.cs:8585` roteia
mensagens `MsgAviso` para o conversor `"Avisos"` (`ConversorEnvioAPI.Impl.Avisos.v10`,
registrado em 8571-8573).

Ingestão/parse — `SPI.Core.Application.decompiled.cs:5949-5980`
(`RetornoLegado.Handlers.ADMI004.v10.TraduzRetorno`):
- `DtHrAviso = AppHdr/CreDt`;
- itera `Document/SysEvtNtfctn/EvtInf` → `EvtCd`, `EvtDesc`;
- devolve `Domain.Model.SPI.ADMI004 { DtHrAviso, EvtCd, EvtDesc }`.

Persistência — `SPI.Core.Infrastructure.decompiled.cs:1852-1870` (`ADMI004Repository`):
upsert por **`DtHrAviso`** (se existe, atualiza `EvtCd`/`EvtDesc`); tabela `SpiADMI004`
(PK `DtHrAviso`, colunas `DtHrAviso datetime2, EvtCd, EvtDesc` — linhas 4576-4587).

Prova de dado vivo: `crk_spi.dbo.SpiADMI004` = **67.822 linhas**, todas
`EvtCd="SPI"`, `EvtDesc="Teste notificação"` (avisos de homolog do BACEN).

Tela — `SPI.Core.Angular.Application.decompiled.cs:742-748` (`TraduzADMI004`) exibe
`SysEvtNtfctn/EvtInf/EvtCd` (rótulo "SPI") e `EvtDesc` (rótulo "AVISO").

### 1.5 admi.005 / admi.006 / admi.007 — ausentes

Nenhuma ocorrência em `.decompiled.cs`; nenhuma linha em `SpiCadMessage`/`SpiCadMessageVigencia`;
nenhum enum. **O legado não conhece admi.005/006/007** (não há avisos operacionais de
janela/parâmetro/manutenção nessa família no catálogo SPI usado pelo legado).

---

## 2. Comportamento do NOSSO código (com prova)

### 2.1 Detecção e roteamento

`apps/spi_service/lib/spi_service/workers/inbound_processor.ex:210-211`:
`"admi.002" -> process_system_event`; `"admi.004" -> process_system_event`.
`apps/shared/lib/shared/bacen/iso20022/message_parser.ex:115-116`: detecta
`SysEvtNtfctn → "admi.004"`, `admi.002.001.01 → "admi.002"`.
`message_parser.ex:83-84,196` e `inbound_processor.ex:16,196`: comentários explícitos —
`admi.005/006/007/009/011` removidas por serem fictícias (fora do XSD raw v5.12 SPI).

### 2.2 admi.002 INBOUND — tratamento amplo (mais que o legado)

`process_system_event` (inbound_processor.ex:1777-1811): grava auditoria em
`bacen_inbound` (`upsert_inbound`, `processing_status=PROCESSED`), broadcast
`system.event`, e chama TRÊS correlações:

1. `maybe_publish_admi002_rejection` (1931-2053): resolve `related_ref = RltdRef/Ref`
   e correlaciona **DIRETO** à transação de saída (`find_correlated_transaction` por
   `original_message_id`/`resource_id`) → publica status `"rejected"` ao Core com
   `reason_code="ADMI002"`, `amount`, `block_id` (libera hold). Também trata **fan-out
   de lote** (`OutboundSendClaim.members_of_batch`, 1944-2014). **O legado NÃO tem esse
   caminho direto** (o `ImportaADMI002` legado só reage a camt.060).
2. `maybe_correlate_admi002_reda` (1828-1851): reflete a recusa no participante indireto
   REDA (`reda_row_for_admi002_ref`: `RltdRef → bacen_outbound.resource_id →
   bacen_outbound.message_id → Shared.Reda.get_by_request_msg_id`), transições
   PENDING→REJECTED / DEREGISTERING→ACTIVE (1916-1929). Sem equivalente no legado.
3. `maybe_correlate_admi002_camt060` (1858-1888): extrai IDs `M+ISPB(8)+23` do XML e
   fecha a `camt060_requests` como `not_found` (`Camt060Requests.correlate_not_found`,
   camt060_requests.ex:160-188). **Fecha a REQUISIÇÃO camt.060**, não a operação
   original.

`operation_query.ex:30,141-150`: a consulta síncrona de operação reporta `"not_found"`
ao chamador; `stuck_outbound_checker.ex:11-12` é **detecção pura** — "o desfecho
(reprocesso, consulta pacs.028, reconciliação camt.060) é decisão do operador".
→ **Nós NÃO rejeitamos automaticamente a pacs.008/pacs.004 original** quando a camt.060
volta "não existe" (diferente do `RejeitaPgtoPorADMI002` legado).

### 2.3 admi.002 OUTBOUND — só no simulador; produção usa pacs.002 RJCT

`message_builder.ex:1298-1324` (`build_admi002`): monta `admi.002.001.01` completo
(`RltdRef/Ref`, `Rsn/RjctgPtyRsn`, `RjctnDtTm`, `ErrLctn`, `RsnDesc`, `AddtlData`).
**Único chamador**: `apps/shared/lib/shared/bacen/simulator/response_generator.ex:732`
(simula o BACEN recusando nossa mensagem). Não há chamador de produção
(`build_admi002` só existe em message_builder + simulador; grep confirma).

Caminho real de recusa de mensagem recebida — `inbound_processor.ex`:
- `maybe_validation_check_pacs008` (285-307): falha de XSD do pacs.008 é **advisory-only**
  ("o BACEN NÃO entrega mensagem inválida … o defeito é NOSSO … SEGUE o processamento",
  linhas 292-305) — **não rejeita, não envia nada**.
- `maybe_signature_check` (318-367): falha de assinatura em qualquer tipo de
  `@signature_required_types` (pacs.008/002/004, pain.014, camt.025/029/053/054/055)
  chama `record_rejected_inbound`.
- `record_rejected_inbound` (1223-1283): grava `bacen_inbound` com `message_type`
  **hardcoded "pacs.008"**, publica `monetarie.spi.transaction.rejected`, e envia
  **`send_pacs002_response(message, "RJCT", …)`** (linha ~1274). **Nunca envia admi.002.**

→ Produção: recusa de mensagem recebida = **pacs.002 RJCT** (e apenas para o contexto
pacs.008; assinatura falha em camt.054, por ex., ainda gera resposta pacs.002).
O legado = **admi.002** (MessageReject de camada de mensagem).

### 2.4 admi.004 INBOUND — auditoria crua, sem estrutura nem tela

`process_system_event` (1777-1811) grava só `bacen_inbound` (ispb, resource_id,
`message_type="admi.004"`, `xml_content` cru, `processing_status=PROCESSED`) e faz
broadcast `system.event` com `event_code: message["event_code"]` /
`event_type: message["message_type"]`. **`message["event_code"]`/`event_description`
NÃO são populados a partir do XML** — o `message_parser` não extrai `EvtCd`/`EvtDesc`
(grep: nenhum `EvtCd`/`EvtDesc` no parser; só no `message_builder`/`message_schemas`).
Front Vue: `grep -rni admi pix/frontend/src` = **zero** (não há tela de aviso admi.004).

→ Sem tabela dedicada (equivalente a `SpiADMI004`), sem parse de EvtCd/EvtDesc, sem
dedup por data do aviso, sem exibição. Só o XML cru fica no `bacen_inbound` (auditoria).

### 2.5 admi.005/006/007 — ausentes por design

`spi_client.ex:500-503`: `get_operational_schedule (admi.005)`,
`get_system_parameters (admi.006)`, `get_maintenance_windows (admi.007)` **removidas**
("não em XSDs raw v5.12 SPI oficial"). `message_form_catalog.ex:572-573`,
`message_parser.ex:83-84`, `inbound_processor.ex:196` reforçam a remoção. **Paridade
com a ausência do legado.**

---

## 3. Tabela de GAPS

| id | título | tipo | risco |
|---|---|---|---|
| g1 | admi.002 OUTBOUND (recusa de mensagem recebida) não é enviado; usamos pacs.002 RJCT | divergencia | medio |
| g2 | camt.060 "não existe lançamento" não rejeita automaticamente a operação original | divergencia | medio |
| g3 | admi.004: sem persistência estruturada (EvtCd/EvtDesc) nem tela; só XML cru de auditoria | parcial | baixo |
| g4 | camt.060: fechamos not_found para qualquer motivo; legado exige RsnDesc literal | divergencia | baixo |
| g5 | Parser admi.002 não extrai RjctnDtTm/ErrLctn/AddtlData (legado extrai p/ diagnóstico) | parcial | baixo |
| c1 | admi.005/006/007 ausentes nos dois lados (fictícias, fora do v5.12) | coberto | info |
| c2 | admi.002 INBOUND: correlação DIRETA + REDA + camt.060 (mais amplo que o legado) | coberto | info |
| c3 | Detecção/roteamento admi.002 e admi.004 presentes e alinhados ao XSD | coberto | info |
| c4 | admi.002 auditado em bacen_inbound (o legado nem persiste admi.002) | coberto | info |

---

## 4. Observações honestas / limites

- Não carreguei o catálogo/XSD oficial BACEN v5.12 nesta auditoria; a afirmação
  "admi.002 é a resposta de erro de camada de mensagem" é lastreada no **catálogo do
  legado** (`SpiCadMessage.CdMsgErro=ADMI.002` para todos os tipos) e no builder legado,
  que é a referência disponível. Se o BACEN exige admi.002 nesse caso, g1 é conformidade;
  se aceita pacs.002 RJCT para pacs.008, g1 é só cosmético — **verdict INCONCLUSIVO
  quanto à exigência BACEN**, CONFIRMADO quanto à divergência legado×nosso.
- g2 tem atenuante real: nosso admi.002 INBOUND direto (2.2/1) cobre a rejeição de
  mensagem por `RltdRef` melhor que o legado; a lacuna é só o caminho *reconciliatório*
  camt.060→rejeita-original, que no nosso desenho é decisão do operador (StuckOutbound).
