# Dossie de paridade PROFUNDO — pibr.001 / pibr.002 (echo / heartbeat)

Auditor: revisao senior PIX. Metodo: READ-ONLY, zero inferencia. Cada afirmacao
tem prova (arquivo:linha ou tabela). Onde nao ha prova, o veredito e INCONCLUSIVO.

Escopo: mensagem de eco (teste de conectividade / heartbeat) do SPI.
- Legado: `AutomacaoPIBR001` (worker de envio automatico de eco), validacao de
  entrada, correlacao pibr.001<->pibr.002 por `Data`/`OrgnlData`, ciclo de status.
- Nosso: `EchoProbeWorker` (sonda de saude), `Shared.Bacen.SpiClient.echo/1`,
  `MessageBuilder`/`MessageParser` (pibr.001/002), correlacao por `Data`/`OrgnlData`.

---

## 1. LEGADO — comportamento com prova

### 1.1 Modelo de mensagem e catalogo

Prova (DB vivo `CRK_SPIDOMINIO.dbo.SpiCadMessage`):

```
CdMsg     CdMsgResposta  CdMsgErro  Classificacao  Caracteristica  CdMsgsRespondidas
PIBR.001  PIBR.002       ADMI.002   1              2 (Echo)        NULL
PIBR.002  NULL           NULL       2              3 (Resposta)    PIBR.001
```

- `Caracteristica` 2 = Echo, 3 = Resposta (prova: `SPI.Core.General.decompiled.cs:7724-7746`,
  enum `enumCaracteristicaMensagem`: `Pagamento=1, Echo, Resposta, ...`).
- PIBR.001 e a mensagem de ECO; sua resposta e PIBR.002; seu erro e ADMI.002.
- PIBR.002 e a RESPOSTA que "responde" PIBR.001 (`CdMsgsRespondidas = PIBR.001`).

Versoes/XSD (DB `SpiCadMessageVigencia`):
`pibr.001.spi.1.0/1.1/1.2/1.3` e `pibr.002.spi.1.0/1.1/1.2/1.3`
(XSD `pibr.001.spi.1.3.xsd`, `pibr.002.spi.1.3.xsd`; vigencia 1.3 desde 2025-08-04).

### 1.2 XML do eco (Book v5.11.1)

Prova: `SPI.Core.Mensageria.Application.Book.v111.decompiled.cs:1539,1604,1752,1842`
(monta EchoReq) e `:1137,1202,1350,1440` (monta EchoRpt).

PIBR.001 (EchoReq — o pedido de eco que enviamos):
```xml
<Document><EchoReq>
  <GrpHdr><MsgId>{msg.MessageId}</MsgId><CreDtTm>{DtHrMsg}</CreDtTm></GrpHdr>
  <EchoTxInf><Data>{msg.Ustrd}</Data></EchoTxInf>
</EchoReq></Document>
```

PIBR.002 (EchoRpt — a resposta de eco):
```xml
<Document><EchoRpt>
  <GrpHdr><MsgId>{msg.MessageId}</MsgId><CreDtTm>{DtHrMsg}</CreDtTm></GrpHdr>
  <EchoTxInf><OrgnlData>{msg.Ustrd}</OrgnlData></EchoTxInf>
</EchoRpt></Document>
```

Chave do eco: o campo `Data` (EchoReq) e ecoado como `OrgnlData` (EchoRpt).
O conteudo desses campos e o `Ustrd`.

### 1.3 Envio automatico do eco (AutomacaoPIBR001)

Enum: `SPI.Core.General.decompiled.cs:8456-8464`
```
enumTipoAutomacao { AutomacaoPIBR001 = 1 [Description "AUTOMACAOECHO"], AutomacaoCAMT060 }
```

Worker de fundo: `SPI.Core.Worker.EnvioAutomacaoMensagem.decompiled.cs:166-211`
- `WorkerEnvioAutomacaoMensagem : BackgroundService`; loop `while (!cancel)` chama
  `IniciaProcessamento()` e depois `Task.Delay(MILISSEGUNDOS_PAUSA_THREAD)`.
- Pausa configuravel pelo parametro `TempoPausaThreadEnvioAutomacaoMensagem`
  (`:213-225`), MINIMO 60_000 ms (`SPIConfigEnvioAutomacaoMensagem`,
  `...Application.decompiled.cs:728-746`: `MILISSEGUNDOS_PAUSA_THREAD_MINIMO = 60000`,
  clamp para no minimo 60s).

Cadencia por configuracao (`...Application.decompiled.cs:466-504`, `IniciaProcessamento`):
- Le TODAS as linhas de `SpiCadParamAutomacaoMsg` (`_automacaoMensagemRepository.List()`;
  mapeada a essa tabela em `...Infrastructure.decompiled.cs:344`, `eb.ToTable("SpiCadParamAutomacaoMsg")`).
- Para cada linha decide 3 janelas:
  - `bValidaData`: data corrente dentro de `[DataDe, DataAte]` (`:477`).
  - `bValidaHora`: `HoraDe <= agora <= HoraAte` (`:482`).
  - `bValidaPeriodoExecucao`: se `UltimaExecucao` setada, so executa se
    `agora >= UltimaExecucao + Periodicidade(horas+min)` (`:485-493`).
- Se as 3 valem, chama `Executa(item)` (`:499-501`).

Construcao do eco (`...Application.decompiled.cs:248-262`, ramo `CdMsg == "PIBR.001"`):
```csharp
new Pibr001APIDTO {
  IdSystem, DtHrOperacao = DateTime.Now,
  IspbDebtor = <ISPB da instituicao>,
  UniqueId = Guid.NewGuid().ToString(),
  Ustrd = $"{IdInstFinanc}-{DateTime.Now:dd/MM/yyyy HH:mm:ss.fff}",   // token unico do eco
  IdFilialInst, IdInstFinanc,
  Canal = (CanalSecundario ? 1 : 0)                                   // canal escolhido pela config
}
```
- Log evt "Automacao de Mensagem - Envio - Mensagem ECO" (`:261`).
- Insere na fila `EntradaAPISEC` se `CanalSecundario` senao `EntradaAPI`
  (`:284-329`) — ou seja, UM canal por linha de config.
- Grava `LogAutomacaoMsg` (`:263-272`) — nota: `IdAutomacao` gravado como
  `AutomacaoCAMT060` mesmo para o eco (quirk do legado, `:268`).
- Atualiza `UltimaExecucao = DateTime.Now` (`:335-336`).

O DTO da API do eco (`SPI.Core.General.decompiled.cs:10741-10758`, `Pibr001APIDTO`):
campos `IdSystem, UniqueId, DtHrOperacao, IdInstFinanc, IdFilialInst, IspbDebtor,
Ustrd, Canal`.

Nota: a classe `EnvioAutomatico` esta DUPLICADA em
`SPI.Core.Worker.TarefasAutomaticas.Application.decompiled.cs:239,562` com a mesma
logica de PIBR.001.

### 1.4 Config UI e parametro do eco

Tela: `LegadoPIX/Pix/SPI/angular/src/app/pages/spi/parametrizacoes/parametrizacoes.component.html:275-317`
— card "Automacao de Mensagens PIBR.001" com checkbox `chkAtivarEco`
("Ativar automacao de mensagens ECO") e seletor de horarios `cboHoraEco`.

Persistencia (`parametrizacoes.component.ts:268-270,309`):
```ts
automacaoecho = '{"Ativa": <bool>, "Horarios": <lista>}'
lista push: { idParam: "AUTOMACAOECHO", vlrParam: automacaoecho, idInstFinanc: null }
```

Valor VIVO no DB (`crk_spi.dbo.SpiCadParam`, `IdParam='AUTOMACAOECHO'`):
```
{"Ativa": true, "Intervalo": "00:15"}
```
(o formato evoluiu de "Horarios" para "Intervalo"; eco ativo com intervalo de 15 min).

### 1.5 Recepcao / validacao do eco de entrada

`ValidaPibr001UseCase.ValidaDados` (`SPI.Core.Application.decompiled.cs:4521-4557`):
valida ISPB/empresa/filial/sistema, IspbDebtor, IspbCreditor, DtHrOperacao,
XmlMsg, DtHrMsg, UniqueId, MessageId e a mensagem de eco.
- `ValidaMensagemEcho` (`:4572-4575`): `Ustrd` OBRIGATORIO, string, MAX 35 chars.
- Se `Origem == RetornoAPIBacen` exige `MessageIdOrig` (`ValidaMessageId`, `:4577-4588`).

Geracao da RESPOSTA pibr.002 quando somos respondentes (Sentido=Retorno):
`SPI.Core.Mensageria.Geral.Application.decompiled.cs:1490-1545`
(`ConvertToPibr001FullDTO`): troca CdMsg->PIBR.002, inverte IspbDebtor/IspbCreditor,
novo `MessageId`, `Origem = RetornoAPIBacen`.

### 1.6 Correlacao pibr.001<->pibr.002 por Data/OrgnlData (agrupamento do eco)

Parse do retorno (`SPI.Core.Mensageria.Geral.Application.decompiled.cs:3209-3245`,
`TrataTagMsg.LerEndToEndIdRetorno`): para cada tag repetida extrai
`OrigUstrd = EchoTxInf/OrgnlData` (`:3230-3232`, via `tags.LeitorE2EId.TagUstrd`).

Tela traducao (`SPI.Core.Angular.Application.decompiled.cs:1362-1370`, `TraduzPIBR002`):
le `Envelope/Document/EchoRpt/EchoTxInf/OrgnlData` rotulado "DADOS ORIGINAIS".

Correlacao (`SPI.Core.Infrastructure.decompiled.cs:2226-2251`,
`FindByListDadosEndToEndId`):
```csharp
respostasEcho = endToEndIds
  .Where(d => d.CdMsg.In(cdMsgsResposta) && d.CdMsg.In(msgsEcho.Select(m => m.CdMsgResposta)))
  .Select(d => d.OrigUstrd);                                  // OrgnlData da pibr.002
lstRspEcho = MessageFull
  .Where(d => cdMsgsEcho.Contains(d.CdMsg)
           && respostasEcho.Contains(d.Ustrd)                 // casa com Ustrd da pibr.001
           && !idsStatusFinais.Contains(d.IdStatus));         // que ainda nao e terminal
```
A pibr.001 enviada guarda `Ustrd`; a pibr.002 recebida traz `OrgnlData` (=OrigUstrd);
o par e casado por `OrigUstrd == Ustrd`.

Ciclo de status: ao correlacionar, a operacao de eco vai para
`enumStatusOperacao.Respondida` (`SPI.Core.General.decompiled.cs:7149`), classificada
`TerminoOK` (final) em `:12730-12734`.

### 1.7 Observacao factual do snapshot

- `crk_spi.dbo.SpiCadParamAutomacaoMsg`: apenas linhas `CAMT.060` (4 linhas),
  ZERO linha `PIBR.001` — apesar de `AUTOMACAOECHO` estar `Ativa:true, Intervalo:00:15`.
- `crk_spi.dbo.SpiMessage` e `CRK_SPIMENSAGERIA.dbo.SpiMessage`: ZERO mensagens
  `CdMsg LIKE 'PIBR%'`.
- Leitura: no backup capturado o eco automatico PIBR.001 nao estava materializado
  na tabela de automacao nem gerou trafego de eco. A CAPACIDADE existe em codigo e
  schema; o snapshot nao contem operacao de eco. (Fato, sem inferir intencao.)

---

## 2. NOSSO (Monetarie / Elixir) — comportamento com prova

### 2.1 Sonda de eco periodica (EchoProbeWorker)

`apps/settlement_service/lib/settlement_service/monitoring/echo_probe_worker.ex`:
- GenServer registrado no supervisor: `settlement_service/application.ex:46`.
- Flag `ECHO_PROBE_ENABLED` default **false** (`:63-67`); com flag off o GenServer
  sobe mas NAO agenda nada (`:99-105`).
- Intervalo FIXO default 5 min (`@default_interval_ms = 5 * 60_000`, `:52`),
  sobrescrito por `ECHO_PROBE_INTERVAL_MS` (`:221-232`).
- A cada ciclo faz UMA probe por canal (`probe_now`, `:79-84`); canais default =
  `[:primary]` + `[:secondary]` quando ha `secondary_url` (`:211-219`) — ou seja,
  envia AMBOS os canais no mesmo ciclo.
- Cada probe (`probe_channel`, `:128-158`): chama `client.echo(channel: channel)`;
  persiste evidencia em `monetarie_spi.echo_probes` (`persist_probe`, `:160-184`);
  alimenta o breaker `ChannelRouter.report_success/failure` (`:144,149`);
  emite telemetry `[:pix, :icom, :echo]` (`:186-194`).
- Tabela evidencia `EchoProbe` (`apps/shared/lib/shared/schemas/spi/echo_probe.ex:21-25`):
  `canal, sent_at, rtt_ms, result, error_reason`. NAO e a tabela de operacoes/mensagens.

Proposito documentado (`:2-43`): sonda de SAUDE do canal que alimenta o breaker de
failover; nao e uma operacao rastreada.

### 2.2 Construcao e envio do eco (SpiClient)

`apps/shared/lib/shared/bacen/spi_client.ex`:
- `echo/1` (`:344-366`): monta pibr.001, assina (`sign_spi`), POST em
  `/api/v1/in/{ispb}/msgs`, devolve `{:ok, %{latency_ms}}`.
- `build_pibr001_request/2` (`:614-638`):
  ```
  <Document><EchoReq><GrpHdr><MsgId>..</MsgId><CreDtTm>..</CreDtTm></GrpHdr>
  <EchoTxInf><Data>{echo_data}</Data></EchoTxInf></EchoReq></Document>
  ```
  `echo_data` default `default_echo_data/0` (`:645-647`): `"MONETARIE_HML_" <> timestamp`,
  clampado a 35 chars (`:617`, `String.slice(0, 35)`).
  AppHdr To = `00038166` (SPI); MsgDefIdr `pibr.001.spi.<versao>` (`:626`).
- `generate_message_id/1` (`:640-643`): `"M" <> ispb <> random` (padrao BizMsgIdr).

Envio manual pela tela "Construir Mensagem"
(`apps/settlement_service/lib/settlement_service_web/controllers/message_controller.ex:1133-1169`):
`dispatch_message("pibr.001", ...)` chama `SpiClient.echo(pibr001_echo_opts(params))`,
repassando o `echo_data` do formulario. NAO expoe selecao de canal (sem `channel:`).

### 2.3 Builder/Parser pibr.001/002

`apps/shared/lib/shared/bacen/iso20022/message_builder.ex`:
- `build_pibr001/1` (`:1255-1273`): `EchoReq/GrpHdr + EchoTxInf/Data` (clamp 35).
- `build_pibr002/1` (`:1275-1291`): `EchoRpt/GrpHdr + EchoTxInf/OrgnlData` (clamp 35).
- Namespace v5.12: `https://www.bcb.gov.br/pi/pibr.001/1.3` (`:1756-1760`).

`apps/shared/lib/shared/bacen/iso20022/message_parser.ex`:
- Deteccao (`:109-110`): `EchoReq`->pibr.001; `EchoRsp` ou `EchoRpt`->pibr.002
  (aceita as duas grafias; legado usa EchoRpt).

XSDs presentes: `apps/shared/priv/xsd/spi/v5.12.1/pibr.001.spi.1.3.xsd`,
`.../pibr.002.spi.1.3.xsd`, e equivalentes em `v5.11.1`.

### 2.4 Correlacao por Data/OrgnlData (agrupamento do eco)

`message_parser.ex:212-239`, `echo_correlation_ref/2`:
- `("pibr.001")` -> extrai `<Data>` (`:213-218`).
- `("pibr.002")` -> extrai `<OrgnlData>` (`:220-225`).
- fail-closed: `nil` para qualquer outro tipo (`:239`).

Perna SENT tagueada com a chave: `apps/shared/lib/shared/audit/bacen_logger.ex:93-115`
```elixir
end_to_end_id =
  MessageParser.extract_end_to_end_id(xml) ||
    MessageParser.echo_correlation_ref(xml, message_type) ||   # eco: Data (pibr.001)
    dict_pi_e2e_fallback(...)
```
Perna RECEIVED tagueada: `apps/spi_service/lib/spi_service/icom/ack_tracker.ex:205-209`
```elixir
end_to_end_id:
  extract_e2e(r.raw_xml) ||
    MessageParser.echo_correlation_ref(r.raw_xml, r.msg_type)  # eco: OrgnlData (pibr.002)
```
Resultado: pibr.001 SENT (chave=Data) e pibr.002 RECEIVED (chave=OrgnlData=Data)
agrupam no Monitor ICOM. Prova de par real citada no codigo: 2026-07-06 13:39 HML
(`message_parser.ex:203-204`).

### 2.5 Recepcao de pibr.002 (resposta de eco)

`apps/spi_service/lib/spi_service/workers/inbound_processor.ex:212` roteia
`"pibr.002" -> process_echo_response`; o handler apenas loga e chama
`process_generic(..., "PROCESSED")` (nao ha operacao de eco a fechar; a sonda vive
em `echo_probes`, nao em `messages`).

Resposta de eco no simulador (`apps/shared/lib/shared/bacen/simulator/response_generator.ex`,
`respond_to_pibr001`): monta pibr.002 com `original_data = echo_correlation_ref(req, "pibr.001")`
(OrgnlData ecoa o Data), novo MsgId.

### 2.6 Automacao de mensagens agendadas (sibling)

`apps/shared/lib/shared/schemas/automation/scheduled_message_job.ex:10`:
`@valid_message_types ~w(camt.060 reda.016 reda.017 pain.014)` — **pibr.001 NAO
consta**. Alem disso nao ha worker/executor que consuma `scheduled_message_jobs`
(so schema + `Shared.Automation.ScheduledMessages` context + controller admin
`admin/scheduled_messages_controller.ex`; grep nao achou runner de "due jobs").
Logo o eco NAO pode ser agendado por esse mecanismo e o unico gerador de eco e o
`EchoProbeWorker` (intervalo fixo, global).

---

## 3. GAPS e itens COBERTOS

### COBERTO

- **[C1] Agrupamento do eco por Data/OrgnlData** — nucleo da pista. Ambos casam
  pibr.001 `<Data>` = pibr.002 `<OrgnlData>`.
  Legado: `SPI.Core.Infrastructure.decompiled.cs:2226-2251` + parse
  `...Mensageria.Geral.Application.decompiled.cs:3230-3232`.
  Nosso: `message_parser.ex:212-225` + `bacen_logger.ex:100-103` (SENT) +
  `ack_tracker.ex:205-209` (RECEIVED).
- **[C2] XML EchoReq (pibr.001)** — estrutura identica
  (`EchoReq/GrpHdr/MsgId+CreDtTm` + `EchoTxInf/Data`).
  Legado `Book.v111:1539,1604`; nosso `spi_client.ex:630-635`, `message_builder.ex:1260-1272`.
- **[C3] XML EchoRpt (pibr.002)** — estrutura identica
  (`EchoRpt/GrpHdr` + `EchoTxInf/OrgnlData`).
  Legado `Book.v111:1137,1202`; nosso `message_builder.ex:1278-1290`.
- **[C4] Limite de 35 chars do Data/Ustrd** — ambos aplicam.
  Legado `ValidaMensagemEcho` (`SPI.Core.Application.decompiled.cs:4574`, max 35);
  nosso `spi_client.ex:617` e `message_builder.ex:1268,1286` (`String.slice(0,35)`).
- **[C5] MsgDefIdr/XSD** — ambos usam `pibr.001.spi.1.3` / `pibr.002.spi.1.3`.
  Legado `SpiCadMessageVigencia`; nosso `spi_client.ex:626`, XSDs em `priv/xsd/spi/v5.12.1`.
- **[C6] Geracao da resposta pibr.002 (OrgnlData ecoa Data)** — coberto.
  Legado `ConvertToPibr001FullDTO` (`...Mensageria.Geral.Application.decompiled.cs:1490-1545`);
  nosso simulador `response_generator.ex respond_to_pibr001`.

### GAPS

- **[G1] Modelo de cadencia divergente** (divergencia, risco medio).
  Legado: cadencia CONFIGURAVEL POR INSTITUICAO — janela de horario
  (`HoraDe..HoraAte`) + `Periodicidade` (h+min) + janela de data (`DataDe..DataAte`),
  materializada em `SpiCadParamAutomacaoMsg`, dirigida por worker que acorda no
  minimo a cada 60s; UI param `AUTOMACAOECHO {"Ativa":true,"Intervalo":"00:15"}`
  (15 min). Prova: `...EnvioAutomacaoMensagem.Application.decompiled.cs:466-504`,
  `...decompiled.cs:178,208`, `SpiCadParam` (DB).
  Nosso: intervalo FIXO GLOBAL (default 5 min via `ECHO_PROBE_INTERVAL_MS`), sem
  por-instituicao, sem janela de horario/data, flag `ECHO_PROBE_ENABLED` default
  false. Prova: `echo_probe_worker.ex:52,63-67,99-105,221-232`.

- **[G2] Natureza do eco: operacao rastreada vs sonda de saude** (divergencia,
  risco baixo/info). Legado: o eco e OPERACAO SPI real (`SpiMessage`) com ciclo de
  status ate `Respondida` (terminal OK) apos correlacao da pibr.002, com log
  `LogAutomacaoMsg` e erro correlacionado a ADMI.002. Prova:
  `SpiCadMessage.CdMsgErro=ADMI.002`, status `Respondida`
  (`SPI.Core.General.decompiled.cs:7149,12730-12734`),
  `...EnvioAutomacaoMensagem.Application.decompiled.cs:263-272`.
  Nosso: o eco e SONDA de saude que alimenta o circuit breaker; evidencia em
  `monetarie_spi.echo_probes` (nao em `messages`); pibr.002 recebida so loga +
  `PROCESSED`, sem fechar operacao nem status `Respondida`. Prova:
  `echo_probe_worker.ex:2-43,160-184`, `inbound_processor.ex:212` +
  `process_echo_response`.

- **[G3] Selecao de canal por config vs ambos os canais** (parcial, risco baixo).
  Legado: cada linha de config envia o eco a UM canal (`CanalSecundario` ->
  `EntradaAPISEC`, senao `EntradaAPI`). Prova:
  `...EnvioAutomacaoMensagem.Application.decompiled.cs:259,284-329`.
  Nosso: `EchoProbeWorker` sonda AMBOS (primario e secundario) no mesmo ciclo
  quando ha `secondary_url`; envio manual nao expoe canal. Prova:
  `echo_probe_worker.ex:211-219`, `message_controller.ex:1164-1169`.
  (Intencao diferente e legitima: sondamos os dois breakers.)

- **[G4] Configuracao operacional do eco (UI/por-instituicao) ausente**
  (ausente, risco baixo). Legado: tela `parametrizacoes` "Automacao de Mensagens
  PIBR.001" habilita/parametriza o eco por instituicao com horarios/intervalo.
  Prova: `parametrizacoes.component.html:275-317`, `parametrizacoes.component.ts:268-309`.
  Nosso: nao ha UI de configuracao do eco; so env (`ECHO_PROBE_ENABLED`,
  `ECHO_PROBE_INTERVAL_MS`). O mecanismo `scheduled_message_jobs` (crontab, por-ISPB)
  NAO aceita pibr.001 (`scheduled_message_job.ex:10`) e nao possui executor. Prova:
  `scheduled_message_job.ex:10`, ausencia de runner (grep).

### INCONCLUSIVO / observacao

- No snapshot legado nao ha linha `PIBR.001` em `SpiCadParamAutomacaoMsg` nem
  mensagem `PIBR%` em `SpiMessage`, apesar de `AUTOMACAOECHO` estar ativo com
  intervalo 00:15. Como a materializacao do param JSON em linha de automacao nao
  foi localizada no codigo decompilado, o veredito sobre "o eco automatico estava
  de fato disparando no ambiente capturado" e INCONCLUSIVO (fato observado: 0
  linhas, 0 mensagens de eco).
