# Dossie de paridade profundo: DICT CID sync (files / entries / events)

Auditor: engenharia PIX (read-only). Data: 2026-07-23.
Regra aplicada: zero inferencia. Toda afirmacao tem prova arquivo:linha ou tabela.coluna.
Onde nao houve prova suficiente, o veredito e INCONCLUSIVO.

Escopo: sincronismo de CIDs do DICT (Catalogo de Identificadores) entre a instituicao
e o BACEN — VSYNC (verificador de sincronismo), arquivo de CIDs (/cids/files), consulta
de vinculo por CID (/cids/entries) e eventos de CID (/cids/events). FOCO pedido: categorias
de horario A/B/C/D e bloqueio eventual 20h-08h.

Legenda de fontes:
- LEGADO codigo: `/Users/luizpenha/monetarie/.scratch/legado-pix-decompiled/*.decompiled.cs`
- LEGADO DB (SQL Server vivo): banco `des_4dict` (container `monetarie-bak-mssql`)
- NOSSO codigo: `/Users/luizpenha/monetarie/pix/backend/apps/{dict_service,shared}`
- NOSSO DB: `apps/shared/priv/repo/sql/mon_pix_schema_clean.sql`

---

## 1. Arquitetura do LEGADO (com prova)

### 1.1 Worker de sincronismo (loop continuo)

`DICT.Core.Worker.Sincronismo.decompiled.cs`:
- `WorkerSincronismo : BackgroundService` roda em loop `while (!stoppingToken.IsCancellationRequested)` (L120).
- Para CADA participante controlado e CADA tipo de chave chama o sincronismo:
  ```
  foreach (string itemISPB in parametroReadRepo.ParticipantesControlados)   // L126
    foreach (enumKeyType tipoChave in lstTipoChave)                          // L129
      await sincronismoUseCase.Processar(parametroReadRepo.NR_ISPB, itemISPB, tipoChave, stoppingToken);  // L131
  ```
- `lstTipoChave` = `{ CPF, CNPJ, PHONE, EMAIL, EVP }` (L103-110).
- Intervalo entre ciclos: `Task.Delay(parametroReadRepo.NR_QtSegundosIntervaloWorkerSincronismo * 1000)` (L135).
- `ParticipantesControlados` e `List<string>` de config (interface `IParametroRepository`,
  `DICT.Core.Application.decompiled.cs:7151`; impl carrega de parametro JSON,
  `DICT.Core.Infrastructure.decompiled.cs:9138` e `:9300`). MULTI-TENANT: o legado sincroniza
  a base DICT de VARIOS participantes, assinando com `NR_ISPB`.
- `StopAsync` (L139-150): ao parar o worker, para cada participante controlado executa
  `validaHorarioUseCase.AlteraSituacaoBloqueio(enumTipoOperacao.VSync, participante, novoBloqueioEventual: false, null)`
  — DESBLOQUEIA o horario eventual (limpa o bloqueio deixado por um sync interrompido).

Prova DB (multi-participante e volume real):
- `des_4dict.dbo.TB_CONTADICT` tem 2 participantes: `12345678` (2096 contas) e `87654321` (8).
- `des_4dict.dbo.TB_HISTSINCRONISMO` = 9591 linhas; participantes distintos: `12345678`, `87654321`.

### 1.2 Cascata VSync -> Log -> Base (SincronismoUseCase.Processar)

`DICT.Core.Application.decompiled.cs:2097-2156` (`SincronismoUseCase.Processar`):
1. `ValidaHorario(VSync, tipoChave, ispbSincronismo)` (L2102). Se NAO OK, ABORTA o sync (L2103-2108).
2. `_conta.PreparaContaSincronismo(ispbSincronismo, (int)tipoChave)` (L2110) — dedup de contas.
3. `AlteraSituacaoBloqueio(VSync, ispb, novoBloqueioEventual: true, tipoChave)` (L2112) — LIGA bloqueio eventual.
4. `_validaSincronismoVSync.Processar(...)` (L2114). Se OK, encerra (base ja sincronizada).
5. Se VSync NAO OK -> `_validaSincronismoLog.Processar(...)` (L2119).
6. Se Log NAO OK -> `_validaSincronismoBase.Processar(...)` (L2124).
7. `AlteraSituacaoBloqueio(..., novoBloqueioEventual: false, ...)` (L2133) — DESLIGA bloqueio eventual.
8. `catch`: em excecao tambem chama `AlteraSituacaoBloqueio(false)` (L2152) — garante desbloqueio.

Ou seja: o legado so baixa o arquivo completo (Base) quando o VSync e o Log falham. Se o
VSync ja bate, NAO ha trafego de arquivo.

### 1.3 VSync (ValidaSincronismoVSyncUseCase)

`DICT.Core.Application.decompiled.cs:3088-3208`:
- `ListSincronismo(tipoChave, nrISPB)` (L3104) retorna os IdCID das contas ATIVAS
  (`DICT.Core.Infrastructure.decompiled.cs:7234-7243`: `WHERE ID_TIPOCHAVE=@ e SPBParticipante=@ e ContaAtiva=1`).
- Verificador = XOR de todos os CIDs: `Validacoes.RetornarHexaString(Validacoes.CalcularXor(list))` (L3111).
  Base vazia -> 64 zeros: `"0000...0000"` (64 chars) (L3111, L3121).
- `CalcularXor` (`DICT.Core.General.decompiled.cs:8857-8872`): XOR bit-a-bit dos byte arrays
  de cada CID (`BitArray.Xor`). NAO e SHA-256. (O CID em si e HMAC-SHA256:
  `CalcularCid`, `DICT.Core.General.decompiled.cs:8845-8848`.)
- `RetornarHexaString` (L8835-8843): hex minusculo (`.ToLower()`).
- Monta `CreateSyncVerificationRequest{ SyncVerification{ KeyType, Participant, ParticipantSyncVerifier=XOR } }`
  (L3125-3134), assina (XMLDSig, L3135) e envia POST `sync-verifications` (L3140).
- Resultado `enumSyncVerificationResult.OK` (L3158) => sucesso; senao ReturnCode -111 (L3166).
- Persiste `HistSincronismo` (IdTipoOperacao=30, VSYNC, Sucesso, situacao) (L3095-3103, L3199-3206).

### 1.4 Log / eventos (ValidaSincronismoLogUseCase)

`DICT.Core.Application.decompiled.cs:2803-2935`:
- Le a ultima sincronizacao: `UltimaSincronizacao(nrISPB, tipoChave)` (L2819). Se null -> ReturnCode -62
  ("Nao foi possivel sincronizar pelo log...") (L2821-2828).
- Consulta `ListarEventosCids` com `StartTime = histSincronismoUltimo.DataAtualizacao` (L2830-2837).
- CONTIGUIDADE: compara `histSincronismoUltimo.VSYNC` com `listCidSetEvents.SyncVerifierStart`
  (L2867-2875). Se o VSYNC ARMAZENADO nao bate com o SyncVerifierStart -> ReturnCode -62 (fallback p/ Base).
- Ordena eventos por `TimeStamp` (L2878). Para cada evento:
  - `ADDED` e nao existe local -> `ProcessaInclusao` (busca `cids/entries/{Cid}` + inclui) (L2890-2893, L2937-3000).
  - `REMOVED` e existe local -> `ProcessaExclusao` (soft-delete) (L2894-2897, L3002-3026).
- Contas locais criadas apos a ultima sync que NAO apareceram no log sao excluidas localmente
  (`RECONCILIATION`) (L2904-2930).
- Ao fim re-roda VSync (L2931). Persiste HistSincronismo IdTipoOperacao=31.

### 1.5 Base / arquivo (ValidaSincronismoBaseUseCase)

`DICT.Core.Application.decompiled.cs:2423-2551` + `LerArquivoAsync` L2622-2745:
- POST `cids/files` (`CreateCidSetFileRequest{ Participant, KeyType }`) assinado (L2439-2448).
- Poll `GetCidSetFile` ate `AVAILABLE`/`ERROR`, com `Task.Delay(NR_QtSegundosIntervaloConsultaArquivo*1000)`
  (L2472-2510). Guarda `URL`, `QtBytes`, `SHA256` do arquivo (L2490-2492).
- `LerArquivoAsync` (L2622): baixa o arquivo (`DownloadFile(url)`, L2626), split por `\n` (uma linha = um CID)
  (L2629), diff bidirecional:
  - CID local nao presente no arquivo BACEN -> exclui local (`ExcluiChaveSincronismo`,
    `TpMotivoOperacao=RECONCILIATION`) (L2647-2666).
  - CID no arquivo BACEN ausente local -> busca `cids/entries/{cid}` (ate 3 tentativas) +
    inclui (`IncluiChaveSincronismo`, `TpMotivoOperacao=RECONCILIATION`) (L2667-2730).
- Ao fim re-roda VSync (L2516). Persiste HistSincronismo IdTipoOperacao=32.

Prova DB (o Base rodou e gravou SHA256/URL):
- `des_4dict.dbo.TB_HISTSINCRONISMO`: op 30 (Vsync)=5137 (873 sucesso), op 31 (SincLog)=2227, op 32 (SincBase)=2227.
- Ultima linha op 32: `sit=3 (AVAILABLE) sha=779ac0305b10... url=set`.

### 1.6 Categorias de horario A/B/C/D e bloqueio (FOCO)

Enum: `DICT.Core.General.decompiled.cs:13111-13120` — `enumTipoHorario { Categoria_A=1, B=2, C=3, D=4 }`.

Prova DB — `des_4dict.dbo.TB_TIPOHORARIO` (janelas reais):

| ID | TP_HORARIO | HR_INICIO | HR_FIM | HR_INICIOBLOQ | HR_FIMBLOQ | Descricao |
|----|-----------|-----------|--------|---------------|------------|-----------|
| 1 | Categoria A | 00:00 | 00:00 | null | null | 24h/dia, todos os dias, SEM janela de manutencao |
| 2 | Categoria B | 20:00 | 08:00 | 08:00 | 20:00 | 24h/dia, com janela de manutencao EVENTUAL entre 20h e 8h |
| 3 | Categoria C | 08:00 | 20:00 | null | null | de 8h as 20h, todos os dias |
| 4 | Categoria D | 20:00 | 08:00 | null | null | de 20h as 8h, todos os dias |
| 5 | Categoria E | 00:00 | 06:00 | null | null | de 0h as 6h, todos os dias |

`TB_HORARIOPARTICIPANTE` (`NR_SPBPARTICIPANTE, ID_TIPOFUNCIONALIDADE, ID_TIPOHORARIO, IC_BLOQUEIOEVENTUAL, ID_TIPOCHAVE_BLOQUEADA`)
liga cada participante x funcionalidade a uma categoria de horario. Dados de teste:
todos func 1..5 = Categoria A (`12345678` e `87654321`).

Funcionalidades (`des_4dict.dbo.TB_TIPOFUNCIONALIDADE`):
`1=Consultas, 2=Atualizacoes, 3=Sincronismo VSYNC/LOG, 4=Sincronismo Base, 5=Atualizacao Estatisticas`.

Operacoes (`des_4dict.dbo.TB_TIPOOPERACAO`, colunas ID_TIPOOPERACAO/TP_OPERACAO/ID_TIPOFUNCIONALIDADE):
`1=Consulta(func1), 30=Vsync(func3), 31=SincLog(func3), 32=SincBase(func4), 46=Atualizar Estatisticas(func5)`.

Aplicacao das janelas — `ValidaHorarioUseCase.ValidaHorario` (`DICT.Core.Application.decompiled.cs:863-887`):
- Se existe horario com `BloqueioEventual==true` casando a chave -> erro `HorarioInvalido` "O horario nao permite a operacao!" (L869-872).
- Senao verifica se a hora atual esta DENTRO da janela: `EstaNoIntervalo(horaAtual, HoraInicio, HoraFim)` para TODOS os horarios (L876). Fora da janela -> `HorarioInvalido` (L878).
- `EstaNoIntervalo` (L846-861) trata janela que cruza a meia-noite (inicio > fim), ex.: Categoria D 20:00-08:00.

Set/clear do bloqueio eventual — `AlteraSituacaoBloqueio` (L812-844): grava `IC_BLOQUEIOEVENTUAL`
e `ID_TIPOCHAVE_BLOQUEADA` em `TB_HORARIOPARTICIPANTE` (via `SaveAsync`).

Gate de sincronismo em andamento — `DICT.Core.Infrastructure.decompiled.cs:9508`
(`ExisteSincronismoEmExecucao`): `WHERE ID_TIPOFUNCIONALIDADE==3 AND SPBParticipante=@ AND ID_TIPOCHAVE_BLOQUEADA != 5(EVP) AND BloqueioEventual==true`.
Usado por varios workers de reivindicacao/claim para PULAR o processamento enquanto ha um sync ativo
(ex.: `DICT.Core.Application.decompiled.cs:1391`, `:1589`, `:1959`).

Observacao provada: em `ListarHorarios` (`DICT.Core.Infrastructure.decompiled.cs:7817`) para
`tpOperacao==1` (Consulta) o `BloqueioEventual` e FORCADO false — ou seja, a CONSULTA por si nao
e barrada pelo bloqueio eventual do sync; o bloqueio eventual barra novo VSync concorrente do
mesmo tipo e faz os workers de claim pularem. A janela de CATEGORIA (EstaNoIntervalo), essa sim,
barra qualquer operacao daquela funcionalidade fora do horario configurado.

### 1.7 Situacoes de sincronismo (LEGADO)

`des_4dict.dbo.TB_TIPOSITUACAOSINCRONISMO`: `1=REQUESTED, 2=PROCESSING, 3=AVAILABLE, 4=ERROR`.
`des_4dict.dbo.TB_HISTSINCRONISMO` persiste por tentativa: `ID_TIPOCHAVE, DH_ATUALIZACAO,
TX_MENSAGEM, IC_SUCESSO, ID_TIPOOPERACAO(30/31/32), TX_VSYNC(char 64), ID_BACEN, ID_TIPOSITUACAOSINCRONISMO,
TX_URL, QT_BYTES, TX_SHA256`.

---

## 2. Arquitetura do NOSSO codigo (com prova)

### 2.1 GenServer periodico (nao loop-cascata)

`apps/dict_service/lib/dict_service/sync/cid_sync_service.ex`:
- `use GenServer` (L18). Iniciado como singleton de cluster
  (`apps/dict_service/lib/dict_service/application.ex:50-53`: `Shared.ClusterSingleton` com
  `children: [DictService.Sync.CidSyncService]`).
- Dois timers: `@default_sync_interval = :timer.hours(6)` (L26), `@default_event_poll_interval = :timer.minutes(5)` (L27).
- Tipos de chave: `@cid_event_key_types ~w(CPF CNPJ PHONE EMAIL EVP)` (L33).
- ISPB unico: `ispb = Keyword.get(opts, :ispb, Shared.Bacen.Client.ispb())` (L79) — NAO ha loop de participantes.
- Flags DESLIGADAS por default (L81-88): `CID_FULL_SYNC_ENABLED=false`, `CID_EVENT_POLL_ENABLED=false`,
  `CID_ENTRY_INGEST_ENABLED=false` (comentario L112-113: "BACEN limits these endpoints and rejects
  CID event polling for indirect participants"). Em HML o env liga full_sync/event_poll (CLAUDE.md raiz,
  "CID_FULL_SYNC_ENABLED=true, CID_EVENT_POLL_ENABLED=true").
- `sync_in_progress` guarda contra sync concorrente NO PROCESSO (L99, L178-181).

### 2.2 Full sync (equivalente ao Base) — a cada 6h

`do_full_sync` (L189-235): para cada key type chama `sync_key_type_file` (L197-204).
`sync_key_type_file` (L238-272):
- `request_cid_file` -> POST `/cids/files` (`DictClient.create_cid_file`, L633-648).
- `wait_for_file` (L674-702): GET `/cids/files/{FileId}`, ate `AVAILABLE` (max 12 tentativas, backoff).
- `process_cid_file` (L719-749): baixa o `url` (`download_cid_file`, L755-781, com Host sem porta),
  `verify_sha256` (L801-812, `:crypto.hash(:sha256, body)` vs `file.sha256`, ABORTA em mismatch),
  `maybe_gunzip` (L816-822), `parse_cid_lines` (uma linha = um CID, L826-832).
- `reconcile_cid_file` (L835-853): diff bidirecional:
  - BACEN tem e local nao -> `resolve_missing_cids` (`CidEntryIngestor.ingest_cid`, throttle) (L858-868).
  - local ACTIVE ausente no arquivo -> `soft_delete_stale_cids` (`status='DELETED', delete_reason='RECONCILIATION'`,
    nunca toca BLOCKED/DELETED) (L873-896).
- `confirm_sync_verification` (L534-557): POST `/sync-verifications/` com o verifier local (best-effort,
  so loga). Veredito != OK -> ERRO + telemetria `[:monetarie, :dict, :sync_verification, :mismatch]`
  (`handle_sync_verification_result`, L565-586).
- Fecha `monetarie_dict.cid_files` (COMPLETED/FAILED) (L276-290).

### 2.3 Event poll (equivalente ao Log) — a cada 5min

`do_poll_events_enabled` (L302-336): para cada key type, `poll_events_for_key_type` com cursor por keyType.
`poll_event_pages` (L390-444):
- GET `/cids/events` filtrado por `participant`+`keyType`+`startTime`(cursor)+`limit`
  (`DictClient.list_cid_events`, L1590-1626; querystring `cid_events_query`, L1616-1626).
- CONTIGUIDADE VSYNC: `drift?(local_verifier, vstart)` compara o verifier local RECOMPUTADO (dos keys
  ativos correntes) com `SyncVerifierStart` do BACEN (L409-415). Em drift **apenas LOGA** um warning e
  SEGUE com o incremental (comentario L403-408: NAO dispara full resync p/ evitar loop infinito). A
  reconciliacao completa fica para o full sync de 6h.
- `process_events` (L919-926) grava eventos em `monetarie_dict.cid_events` idempotente
  (`insert_cid_event`, `ON CONFLICT (participant_ispb, entry_key_type, cid, event_type) DO NOTHING`, L940-959).
- Paginacao por `HasMoreElements` ate `@max_event_pages=50` (L32, L385-388).
- `maybe_reconcile_entries` (L340-362): so quando `CID_ENTRY_INGEST_ENABLED`; resolve CIDs ADDED nao
  materializados em `keys` (`CidEntryIngestor.reconcile`) e aposenta os REMOVED
  (`CidEntryIngestor.reconcile_removals`).

Observacao: os eventos so viram CHAVES quando `CID_ENTRY_INGEST_ENABLED` esta ligado; sem ele o poll
so grava hashes em `cid_events` (moduledoc `CidEntryIngestor`, L1-23).

### 2.4 Verificador local (XOR) — paridade explicita

`sync_verifier_for_cids/1` (L505-527): XOR bit-a-bit (`:crypto.exor`) dos CIDs (cada 32 bytes),
hex minusculo (`Base.encode16(case: :lower)`); vazio -> `@empty_sync_verifier = String.duplicate("0", 64)`
(L42, L512). Moduledoc L507-509 declara explicitamente paridade com `Validacoes.CalcularXor`, "nao SHA-256".
`active_cids_for_key_type` (L591-603): `SELECT cid FROM monetarie_dict.keys WHERE ispb=$ AND key_type=$
AND status='ACTIVE' AND cid IS NOT NULL`.

### 2.5 Ingestao de vinculo por CID (CidEntryIngestor)

`apps/dict_service/lib/dict_service/sync/cid_entry_ingestor.ex`:
- `ingest_cid` (L50-82): `get_entry_by_cid` -> `upsert_entry` (idempotente em `(key_type, key_value)`).
  404 do BACEN NAO e remocao autoritativa (L69-76). Remocao e event-driven (`reconcile_removals`, L128-151).
- `upsert_entry` (L161-177) + `conflict_query` (L194-216): `ON CONFLICT` GUARDADO — nunca toca
  `BLOCKED`/`DELETED` nem reescreve `status` (nao "des-bloqueia" chave marcada).

### 2.6 Schema NOSSO

`apps/shared/priv/repo/sql/mon_pix_schema_clean.sql`:
- `monetarie_dict.cid_files` (L1965-1977): `request_id, participant_ispb, status(PENDING/PROCESSING/COMPLETED/FAILED),
  file_url, entry_count, error_message, created_at, processing_started_at, completed_at, expires_at`.
- `monetarie_dict.cid_events` (L1948-1958): `id, event_type, cid, entry_key, entry_key_type, file_id,
  participant_ispb, details(jsonb), created_at`.
- `monetarie_dict.sync_log` (L2839-2849): `id, sync_time, institution_id, sync_type, last_version,
  new_version, records_processed, status, error_message`.

NAO existe no schema NOSSO nenhuma tabela equivalente a `TB_HORARIOPARTICIPANTE`, `TB_TIPOHORARIO`,
`TB_HISTSINCRONISMO` (com VSYNC/SHA256 por tentativa) ou `TB_TIPOSITUACAOSINCRONISMO`.

### 2.7 Ausencia de logica de horario/bloqueio (prova negativa)

Grep em `apps/dict_service`, `apps/spi_service`, `apps/shared/lib` por
`categoria|20:00|08:00|janela.*manuten|horario_participante|bloqueio.event|maintenance.window|block.*window`
NAO retorna nenhuma implementacao de janela de horario DICT nem de bloqueio eventual do sync
(as ocorrencias sao de infracao/notification/rate-limit por categoria A-H de LEITURA, nao a janela A/B/C/D).
Nao ha `AlteraSituacaoBloqueio`, `ValidaHorario`, `EstaNoIntervalo` nem `ExisteSincronismoEmExecucao`.

---

## 3. Matriz de paridade e GAPS

### COBERTO

- **C1 VSYNC via XOR (nao SHA-256):** nosso `sync_verifier_for_cids` (L505-527) e o legado
  `CalcularXor`/`RetornarHexaString` (`DICT.Core.General.decompiled.cs:8857-8843`) sao equivalentes;
  base vazia = 64 zeros nos dois (nosso L42/L512; legado L3111/L3121).
- **C2 Base / arquivo:** POST `/cids/files` -> poll `/cids/files/{id}` -> download URL -> verificacao
  SHA-256 do arquivo -> parse linha-a-linha -> diff. Nosso `sync_key_type_file`/`process_cid_file`
  (L238-272, L719-853) espelha `ValidaSincronismoBaseUseCase`/`LerArquivoAsync`.
- **C3 Diff bidirecional + RECONCILIATION:** BACEN-only -> resolve+upsert; local-only ACTIVE ->
  soft-delete `RECONCILIATION`. Nosso `reconcile_cid_file` (L835-896) vs legado L2647-2730.
- **C4 Log / eventos incrementais:** `/cids/events` filtrado por Participant+KeyType com StartTime,
  ADDED/REMOVED. Nosso `poll_event_pages` (L390-444) vs `ValidaSincronismoLogUseCase` (L2803-3026).
- **C5 Contiguidade VSYNC (verifier local vs SyncVerifierStart):** presente nos dois (nosso `drift?`
  L449-458; legado L2867-2875) — porem com semantica diferente de reacao (ver G5) e de base (ver G7).
- **C6 Guarda anti-des-bloqueio:** nosso upsert nunca reativa/des-bloqueia BLOCKED/DELETED
  (`conflict_query`, L194-216) — hardening ALEM do legado.
- **C7 Idempotencia de eventos:** `ON CONFLICT ... DO NOTHING` (L940-959).
- **C8 5 tipos de chave iterados:** CPF/CNPJ/PHONE/EMAIL/EVP nos dois.

### GAPS

- **G1 Categorias de horario A/B/C/D + EstaNoIntervalo (AUSENTE).** Ver secao 4 gap g1.
- **G2 Bloqueio eventual 20h-08h do sync (AUSENTE).** Ver g2.
- **G3 Gate de "sincronismo em execucao" para workers de claim (AUSENTE).** Ver g3.
- **G4 Estrategia de sync (DIVERGENCIA): cascata VSync->Log->Base por ciclo vs full-Base-6h + poll-5min.** Ver g4.
- **G5 Reacao ao drift (DIVERGENCIA): legado auto-cura no ciclo (Log->Base); nos so logamos e seguimos.** Ver g5.
- **G6 Multi-participante ParticipantesControlados vs unico ISPB proprio (DIVERGENCIA/PARCIAL).** Ver g6.
- **G7 Base da contiguidade (PARCIAL): VSYNC ARMAZENADO (legado) vs recomputado corrente (nos).** Ver g7.
- **G8 Historico de sincronismo (PARCIAL): TB_HISTSINCRONISMO rico por tentativa vs sync_log/cid_files coarse.** Ver g8.
- **G9 Flags OFF por default (DIVERGENCIA operacional).** Ver g9.
- **G10 PreparaContaSincronismo dedup de CIDs duplicados antes do XOR (PARCIAL).** Ver g10.

---

## 4. Detalhamento dos gaps (legado x nosso x risco)

### g1 — Janelas de categoria A/B/C/D nao existem no nosso sistema [AUSENTE, ALTO]
- LEGADO: `TB_TIPOHORARIO` define janelas por categoria (Cat C 08-20, Cat D 20-08, Cat B eventual 20-08,
  Cat A/E sem/parcial); `ValidaHorario`+`EstaNoIntervalo` (`DICT.Core.Application.decompiled.cs:863-887, 846-861`)
  BARRAM operacoes DICT fora da janela configurada por participante x funcionalidade (`TB_HORARIOPARTICIPANTE`).
- NOSSO: nenhuma tabela de horario, nenhuma checagem de janela; DICT opera 24x7 sem restricao de faixa horaria
  (grep negativo, secao 2.7). O `CidSyncService` roda por timer fixo (6h/5min) sem consultar janela.
- Risco: ALTO se a instituicao tiver categoria != A no BACEN — operar/sincronizar fora da janela pode
  ser rejeitado pelo BACEN ou violar o contrato de disponibilidade. INCONCLUSIVO qual categoria a Monetarie
  possui hoje no BACEN (sem prova nesta base; dados legados sao de teste = Categoria A).

### g2 — Bloqueio eventual 20h-08h durante o sync nao e replicado [AUSENTE, MEDIO]
- LEGADO: liga/desliga `IC_BLOQUEIOEVENTUAL` em `TB_HORARIOPARTICIPANTE` ao redor do sync
  (`AlteraSituacaoBloqueio(true/false)`, `DICT.Core.Application.decompiled.cs:2112/2133/2152`) e no
  `StopAsync` do worker (`Worker.Sincronismo:147`) garante desbloqueio de sync interrompido; impede novo
  VSync concorrente do mesmo tipo (`ValidaHorario` L869).
- NOSSO: guarda de concorrencia e apenas `sync_in_progress` em memoria do GenServer (L99/L178-181),
  perdida em restart; nao ha estado persistido nem janela de manutencao.
- Risco: MEDIO. Em restart durante um full sync, o guard em memoria some (mitigado por ser singleton
  de cluster e idempotente). Nao ha janela de manutencao anunciada.

### g3 — Gate "existe sincronismo em execucao" para workers de claim [AUSENTE, MEDIO]
- LEGADO: workers de reivindicacao/solicitacao PULAM quando ha sync ativo
  (`ExisteSincronismoEmExecucao`, `DICT.Core.Infrastructure.decompiled.cs:9508`; usos em
  `DICT.Core.Application.decompiled.cs:1391, 1589, 1959`).
- NOSSO: nao existe esse gate; os fluxos de claim/portabilidade/MED nao consultam estado de sync.
- Risco: MEDIO — possivel processamento de claim concorrente com uma reconciliacao de base
  (o legado evitava esse cruzamento). INCONCLUSIVO se causa efeito pratico no nosso desenho event-driven.

### g4 — Estrategia de sincronismo divergente [DIVERGENCIA, MEDIO]
- LEGADO: por ciclo (intervalo `NR_QtSegundosIntervaloWorkerSincronismo`), por participante x tipo:
  VSync primeiro; so cai para Log e depois Base quando o anterior falha
  (`SincronismoUseCase.Processar`, L2114-2131). Nao baixa arquivo quando o VSync ja bate.
- NOSSO: SEMPRE baixa o arquivo completo (Base) a cada 6h (`do_full_sync`, L189-235) e roda VSync apenas
  como confirmacao pos-reconcile (`confirm_sync_verification`, L249/L534); o incremental (Log) e um poll
  separado de 5min.
- Risco: MEDIO — download de arquivo completo periodico e mais pesado/custoso contra o BACEN do que a
  cascata guiada por VSync; cadencia e gatilhos diferentes.

### g5 — Reacao ao drift de VSYNC divergente [DIVERGENCIA, MEDIO]
- LEGADO: drift (SyncVerifierStart != VSYNC local) no Log -> retorna -62 e a cascata cai para o Base
  (arquivo completo) NO MESMO CICLO, auto-curando (L2868-2875 -> `SincronismoUseCase` L2124).
- NOSSO: em drift o poll APENAS loga um warning e segue com o incremental (comentario L403-415);
  a reconciliacao completa fica para o full sync de 6h. Justificativa no codigo: evitar loop infinito
  enquanto o download de arquivo (arq-h) estiver indisponivel.
- Risco: MEDIO — uma divergencia real de base pode persistir ate 6h (ou ate o full sync estar habilitado
  e disponivel), enquanto o legado corrige na hora.

### g6 — Multi-participante (ParticipantesControlados) vs unico ISPB [DIVERGENCIA/PARCIAL, MEDIO]
- LEGADO: itera `ParticipantesControlados` (varios ISPBs), assinando com `NR_ISPB`
  (`Worker.Sincronismo:126-131`); DB confirma 2 participantes distintos em `TB_CONTADICT`/`TB_HISTSINCRONISMO`.
- NOSSO: usa um unico `ispb = Shared.Bacen.Client.ispb()` (L79); nao sincroniza bases de terceiros.
- Risco: MEDIO se a Monetarie precisar sincronizar a base DICT de participantes indiretos com ISPB proprio.
  INCONCLUSIVO: comentario nosso (L112-113) diz que o BACEN rejeita poll de eventos para participantes
  indiretos — o que sugere modelo de participante DIRETO unico e pode tornar o gap NAO-aplicavel. Sem prova
  do arranjo regulatorio atual, mantido como divergencia a validar.

### g7 — Base da contiguidade: armazenado vs recomputado [PARCIAL, BAIXO]
- LEGADO: compara `histSincronismoUltimo.VSYNC` (VSYNC ARMAZENADO da ultima sync) com `SyncVerifierStart`
  (`DICT.Core.Application.decompiled.cs:2867`).
- NOSSO: recomputa `local_sync_verifier` a partir do estado ATUAL de `keys` e compara com `SyncVerifierStart`
  (L380/L409). Sem hist persistido de VSYNC.
- Risco: BAIXO — semanticas de ponto-no-tempo diferentes; podem divergir se a base mudou entre a janela do
  evento e o poll. Efeito pratico limitado pela reconciliacao completa.

### g8 — Historico de sincronismo mais pobre [PARCIAL, BAIXO]
- LEGADO: `TB_HISTSINCRONISMO` grava por tentativa VSYNC, SHA256, URL, QT_BYTES, ID_BACEN, situacao,
  IC_SUCESSO, tipo de operacao (30/31/32) — 9591 linhas de auditoria.
- NOSSO: `sync_log` (sync_type, records_processed, status) + `cid_files` (status/entry_count/error_message
  por arquivo); nao ha VSYNC/SHA256/bytes por tentativa nem separacao VSync/Log/Base.
- Risco: BAIXO — menor rastreabilidade forense do sincronismo.

### g9 — Flags de sync OFF por default [DIVERGENCIA operacional, INFO/MEDIO]
- LEGADO: worker sempre ativo (BackgroundService em loop).
- NOSSO: `CID_FULL_SYNC_ENABLED`, `CID_EVENT_POLL_ENABLED`, `CID_ENTRY_INGEST_ENABLED` = false por default
  (L81-88); ligados via env em HML (CLAUDE.md raiz). Se em algum ambiente ficarem OFF, o sync nao roda
  (apenas `noop_reconciliation`, L903-915).
- Risco: MEDIO se producao ficar sem as flags ligadas (base DICT nao reconcilia); INFO se ligadas.
  INCONCLUSIVO o estado atual das flags em producao (sem prova nesta sessao).

### g10 — Dedup de CIDs duplicados antes do XOR [PARCIAL, BAIXO]
- LEGADO: `PreparaContaSincronismo` (`DICT.Core.Infrastructure.decompiled.cs:7408-7433`) desativa contas
  ativas duplicadas por ID_CID (mantem a mais recente) ANTES de computar o VSYNC, evitando XOR corrompido
  (dois CIDs iguais se cancelam no XOR).
- NOSSO: `active_cids_for_key_type` (L591-603) nao deduplica explicitamente; depende do UNIQUE
  `(key_type, key_value)` do upsert. Nao ha passo de preparo.
- Risco: BAIXO — na pratica o unique constraint reduz duplicidade; sem passo dedicado, um acervo com CID
  repetido produziria verifier errado.

---

## 5. Conclusao

O money-path do CID sync (VSYNC por XOR, arquivo /cids/files com verificacao SHA-256, diff bidirecional
com RECONCILIATION, eventos incrementais /cids/events, resolucao /cids/entries, idempotencia) esta
COBERTO e com paridade declarada e provada, inclusive com hardening extra (guarda anti-des-bloqueio).

O gap central pedido no FOCO — categorias de horario A/B/C/D e bloqueio eventual 20h-08h — esta
AUSENTE no nosso sistema (g1, g2), assim como o gate de sincronismo-em-execucao para workers de claim (g3).
Os demais itens sao divergencias de estrategia/cadencia (g4, g5, g9), de escopo multi-participante (g6) e
de granularidade de auditoria/contiguidade/dedup (g7, g8, g10).

INCONCLUSIVOS que exigem prova externa a esta base: a categoria de horario da Monetarie no BACEN (g1),
o arranjo de participantes indiretos (g6) e o estado das flags de sync em producao (g9).
