# Handoff 2026-07-15 (noite) — CCS/STA: dedup de envio, normalização ACCS002 e o defeito ABERTO da autoridade do ACCS002

> Sessão longa e com deploys em PRODUÇÃO ao vivo enquanto o dono testava CCS. O dono
> pediu para PARAR de mexer em produção, empacotar tudo, garantir commit+push, fazer
> deep audit e deixar um comando de retomada. Este é o fechamento.

## 0. Estado do git (tudo commitado e pushado)

- Branch `main`, HEAD `14e867fb`, `local == origin/main` (0/0), working tree limpo.
- Commits desta sessão sobre `180a1d9e` (merge da sessão paralela do time):
  - `3a978ca9` feat(sta): cabine embeda conteúdo CCS no file.received
  - `6ed4cf0b` feat(ccs): roteia respostas inbound ACCS002/003/004/009 ao módulo CCS
  - `cf067f16` fix(sta-admin): tela Arquivos com dados + tema + sino
  - `71940dd9` feat(core-admin/ccs): aba Linha do tempo (**este é do time**, Eduardo/Fable)
  - `cb391b47` fix(ccs): normaliza formato de transporte do STA (zip/gzip + UTF-16BE)
  - `c283a806` feat(core-admin/ccs): botão Atualizar (lista + dialog)
  - `50357816` fix(sta): upload idempotente — NUNCA enviar arquivo duplicado ao BACEN
  - `14e867fb` fix(ccs): confirmar limpa falha antiga + ACCS002 correlaciona no reenvio + guarda de UI
  - (mais cedo, ancestrais via merge) `daeb9b92` sisbajud varas, `eb18be5d` simulado, `06360617` login STA split-panel

## 1. PROD atual (deployado nesta sessão) + rollbacks

| Serviço | Rev atual | Rollback | O que carrega |
|---|---|---|---|
| `core-api` | **:51** | :50 → :49 → :48 | CCS inbound + normalize + correlação/clears + sisbajud varas |
| `core-admin-ui` | **:22** | :21 → :20 → :19 | timeline CCS + simulado + botões Atualizar + guarda rejection_reason |
| `sta-api` | **:14** | :13 → :12 | embed CCS + file_type + **dedup de upload** |
| `sta-admin-ui` | **:8** | :5 | Arquivos com dados + tema + sino + login split-panel |
| `pix-api` | :51 | — | NÃO tocado nesta sessão |
| `spb-api` | :35 | — | NÃO tocado nesta sessão |

Deploy manual PROD: helper em `<scratchpad>/deploy_prod.sh` (clona a task-def da revisão-fonte
BOA, troca a imagem, VERIFICA a string antes de registrar, atualiza o serviço). Tag padrão
`prod-<sha>-<desc>-20260715`. **GOTCHA que me mordeu:** passar o repo curto (`sta-admin-ui`) em vez
do completo (`monetarie/sta-admin-ui`) gera imagem sem o prefixo `monetarie/` → task-def com
imagem inexistente. O helper agora clona de revisão-fonte explícita e ABORTA se a imagem não
confere. As task-defs quebradas `sta-admin-ui-prod:6/:7` foram desregistradas.

## 2. O que foi entregue (e FUNCIONA, provado ao vivo)

1. **Envio duplicado ao BACEN: ELIMINADO** (`sta-api:14`). Um envio de ACCS001 gerava 2 uploads
   (2 protocolos). Causa: `process_queue` disparado por 2 gatilhos (submit + timer periódico) +
   `mark_uploading` incondicional → 2 workers pegavam o mesmo arquivo "pending". Fix em 2 camadas:
   `Outbound.claim_pending/1` (UPDATE ... WHERE status='pending' atômico) + guarda por
   `active_workers` no Uploader. **Provado em PROD após deploy:** o reenvio de `202607110001`
   gerou 1 upload só. Testes cabine 42/0.
2. **Normalização do ACCS002: FUNCIONA** (`core-api` desde :50). O BACEN entrega o CCS
   COMPACTADO (zip/gzip) e em UTF-16BE (Guia §3.1/§7); o parser assumia XML UTF-8 cru → `:invalid_xml`.
   `StaInbound.normalize_content` descompacta + transcodifica + alinha o `encoding=` antes de parsear.
   **Provado em PROD:** os ACCS002 passaram a parsear (num_remessa extraído). Testado com 6 variantes.
3. **"Falha de envio STA" grudada em arquivo confirmado: RESOLVIDO** (`core-api:51` + dado reparado).
   `build_terminal_attrs("confirmed")` e `mark_submitted` zeram `rejection_reason`/`error_code`;
   a UI só mostra a falha em status rejected/error. **Reparei o dado do `202607080001` em PROD** (1 registro).
4. **Tela STA Arquivos + tema + sino + login split-panel** (`sta-admin-ui:8`).
5. **Botões Atualizar** na tela CCS (lista + dialog), aba Timeline (do time).

## 3. RECLAMAÇÕES ABERTAS DO DONO (não resolvidas — atacar na próxima sessão)

### 3.1. "Você quebrou de NOVO a ACCS002" — DEFEITO REAL, é o item #1
**Sintoma (PROD, provado):** o `202607110001` ficou `status=confirmed`, `accs002_content` NULO, e o
log `[CCS StaInbound] ACCS002 sem ACCS001 correspondente (num_remessa=202607160001)`. A aba ACCS002
fica vazia mesmo com o arquivo "Confirmado".

**Causa-raiz (arquitetural, NÃO é regressão da sessão — é pré-existente que ficou exposto):**
o CALLBACK DE UPLOAD da cabine dispara com status "success" quando o upload ao BACEN completa, e
`StaSubmission.map_sta_to_ccs_status` mapeia "success"/"completed" → **"confirmed"**. Ou seja, o
arquivo vira "confirmed" pelo SUCESSO DO UPLOAD, ANTES do ACCS002 real (a resposta de recepção do
BACEN, com SitArq A/R + conteúdo). Quando o ACCS002 inbound chega:
- o match exato falha (o BACEN numera a remessa pela data de ENVIO = `202607160001`; nosso
  `num_remessa` usa a data do MOVIMENTO = `202607110001`), e
- o fallback `unique_accs002_awaiter` (que procura status "submitted" aguardando ACCS002) não
  acha, porque o arquivo JÁ está "confirmed" (não está mais aguardando).
→ o ACCS002 é descartado, `accs002_content` nunca anexa. **Pior: se o BACEN REJEITAR (SitArq R),
o arquivo fica "confirmed" ERRADO** (o upload deu certo, mas o conteúdo foi recusado).

**Cadeia CONFIRMADA pela auditoria (arquivo:linha):**
1. `submit` reenvia o XML já gravado sem reconstruir (`sta_submission.ex:137`) → o `NumRemessaArq`
   do XML == `ccs_files.num_remessa`. Marca `submitted` (`sta_submission.ex:168-194`).
2. A cabine sobe e dispara o callback de UPLOAD com `status: :success`, payload SEM `accs002_xml`
   (`sta/.../outbound/worker.ex:276-281,428-438`), fire-and-forget sem retry (`worker.ex:421-426`).
3. `apply_callback` → `map_sta_to_ccs_status("success", …)` **→ "confirmed"** (`sta_submission.ex:208-209`)
   → `apply_status_transition` grava `accs002_content: nil`, `confirmed_at` (`sta_submission.ex:270-304`).
4. O ACCS002 inbound chega, `UltNumRemessaArq=202607160001` (data de ENVIO) ≠ `num_remessa=202607110001`
   (data do MOVIMENTO) → match exato nil; fallback `unique_accs002_awaiter` procura `submitted AND
   accs002_received_at IS NULL` mas o arquivo já é `confirmed` → 0 candidatos → `:not_found` →
   `[CCS StaInbound] ACCS002 sem ACCS001 correspondente` + ACK, conteúdo descartado (`sta_inbound.ex:138-140`).
5. **Caso perigoso confirmado:** se o BACEN REJEITAR (SitArq R), `apply_inbound_accs002` bate em
   `ensure_not_terminal` (`sta_submission.ex:501`) → `:already_terminal` → a REJEIÇÃO é descartada e
   o arquivo fica `confirmed` ERRADO.

**FIX CORRETO (o ACCS002 é a autoridade; era o que eu ia aplicar quando o dono mandou parar):**
- **Passo 1** — `map_sta_to_ccs_status` (`sta_submission.ex:205-224`): `success`/`completed`/`uploaded`
  deixam de mapear pra "confirmed"; caem num resultado NÃO-TERMINAL (arquivo fica `submitted`). Vale
  pro `apply_callback` E pro `sync_from_sta` (poller), que leem o mesmo mapeamento.
- **Passo 2 (OBRIGATÓRIO)** — preservar `sta_protocol`: se `success -> nil` cair no catch-all
  `build_terminal_attrs(_, _, now)` (`sta_submission.ex:334-336`), ele só grava `sta_last_sync_at` e
  PERDE o `sta_protocol`. Criar uma transição "uploaded" dedicada que grave `sta_protocol` +
  `sta_last_sync_at` mantendo `status: "submitted"` (WHERE status='submitted').
- **Passo 3 (LATENTE que o fix ATIVA — OBRIGATÓRIO)** — `build_terminal_attrs("confirmed", …)` faz
  `sta_protocol: payload["protocol"] || payload["protocol_number"]` (`sta_submission.ex:291`). Quando
  o ACCS002 confirma, o payload é `%{"accs002_xml" => xml}` (`accs002_terminal("A")`,
  `sta_submission.ex:630-632`) — sem protocolo → gravaria `sta_protocol = NULL`, apagando o do passo 2.
  O confirm por ACCS002 deve PRESERVAR `sta_protocol` (omitir a chave quando nil / COALESCE). Hoje isso
  está mascarado porque o arquivo já vira terminal no upload.
- **Testes que QUEBRAM** em `test/monetarie/use_cases/ccs/sta_submission_test.exs` (reescrever pra
  esperar que success mantém submitted e só o ACCS002 confirma): linhas **164-180** (callback success
  Worker shape), **203-214** (polling completed), **485-502** (sync completed), **565-579** (sync
  atom-keyed completed), **379-390** (response_xml populava accs002_content no success). PASSAM sem
  mudança: 352-363 (alias protocol, se o passo 2 gravar sta_protocol) e 540-563 (confirmed não
  sobrescreve). Falhas `failure -> rejected/error` e ACCS003/009 NÃO são afetados.

**Correlação por PROTOCOLO DE ORIGEM (o certo, mas requer plumbing):** `ccs_files.sta_protocol`
guarda o protocolo do UPLOAD do ACCS001. O ACCS002 é resposta daquele envio. Mas a cabine NÃO captura
`protocoloOrigem` hoje: o parse de `/arquivos/disponiveis` extrai só Protocolo/Sistema/TipoArquivo/
Tamanho/Hash/Situacao/DataHora (`sta/.../sta/client.ex:1509-1533`), sem ProtocoloOrigem; `Types.FileInfo`
não tem o campo (`sta/.../sta/types.ex:10-66`); o `protocol_number` do `file.received` é o da PRÓPRIA
resposta, não o de origem. Plumbing em 4 pontos (cabine parse → FileInfo/metadata → file.received →
Core meta → `resolve_accs002_target` tenta `sta_protocol == protocoloOrigem` primeiro). **Ressalva
(não inventar):** o nome/presença do campo `ProtocoloOrigem` na resposta STA §8.1 para arquivos-CCS
NÃO foi confirmado no repo — validar com amostra REAL do ACCS002 baixado antes de implementar.

**Outros achados da auditoria (verificados, arquivo:linha):**
- **D1** — `num_remessa` é chave frágil; o fallback `unique_accs002_awaiter` FALHA sempre que houver
  ≥2 ACCS001 `submitted` aguardando ao mesmo tempo (retorna nil por ambiguidade, `sta_submission.ex:544-547`).
- **D3** — `reset_to_generated/1` (`sta_submission.ex:647-669`) NÃO limpa `error_code`,
  `accs002_content`, `accs002_received_at`, accs003_*/accs009_* → reset+resubmit carrega lixo do ciclo
  anterior na aba e no rótulo de erro. Defeito real, severidade menor.
- **D4** — `CcsStaStatusPoller` só varre `status == "submitted"` (`ccs_sta_status_poller.ex:42-50`) →
  um arquivo `confirmed` errado nunca é reavaliado. Sob o fix, o arquivo fica `submitted` e o poller
  o mantém quente corretamente.
- **D5** — o callback é `Task.start` sem retry (`worker.ex:421-426`); callback perdido = silêncio. Sob
  o fix, o poller + o ACCS002 inbound cobrem (resiliente).

**IMPORTANTE:** eu NÃO deployei esse fix (o dono mandou parar). O `core-api:51` atual tem a
normalização (funciona) + o fallback + os clears, mas o callback de upload AINDA confirma cedo → o
ACCS002 não anexa. **Este é o trabalho #1 da próxima sessão** (o dedup e a normalização JÁ estão certos).

### 3.2. SISBAJUD: RESP_46026562000105_2026-07-15.zip "Gerado" com 0 REGISTROS — RESÍDUO do código VELHO, JÁ CORRIGIDO no motor
**Sintoma (screenshot do dono):** tela `/dashboard/regulatory/sisbajud` aba Arquivos mostra um
RESP (resposta 5302) "Gerado" com **0 registros**; cards TOTAL/RECEBIDAS/EXECUTADAS/FALHAS = 0 no 15/07.

**Fatos VERIFICADOS (query no `regulatory_files` de PROD + registeredAt das task-defs, não inferido):**
- O RESP real: `RESP_46026562000105_2026-07-15.zip`, `record_count=0`, `status=generated`,
  `metadata.source=sta_inbound`, **inserted_at = 2026-07-15 21:48:39 UTC = 18:48 BRT**.
- O 1º deploy do fix SISBAJUD hoje (`core-api:49`, que contém `daeb9b92`) foi registrado às
  **20:50 BRT**. Ou seja, **o RESP nasceu ~2h ANTES do fix** → foi gerado pelo **código VELHO**.
- O fix `daeb9b92` ESTÁ no PROD atual (`core-api:51 = 14e867fb`, provado por `git merge-base
  --is-ancestor`). **A suposição do agente de que o fix não estava em PROD veio de memória velha
  (`:48`/`2df1c875`) e está REFUTADA.**

**Conclusão:** o código antigo (default `:bloqueio` p/ tudo) force-parseou um arquivo inbound
não-bloqueio (provável 5305/varas, mas o arquivo de ENTRADA exato precisa ser baixado p/ confirmar)
como remessa de bloqueio → registros descartados → 5302 com 0 respostas + `RegulatoryFile` "generated"
persistido. O motor **já está corrigido** (novas ingestões classificam por TIPO_ARQUIVO; 5305 vira
varas sem 5302). **Mas o fix é PREVENTIVO, não corretivo** (achado B4): o RESP bogus já gravado
CONTINUA na aba até ser excluído.

**Ação (próxima sessão):** (1) baixar o RESP e/ou o arquivo de entrada p/ confirmar que era 5305;
(2) excluir o `RegulatoryFile` bogus (endpoint `DELETE /regulatory/files/:id` + botão na tela); (3)
cards zerados são CONSISTENTES com "nenhuma ordem 5301 no dia" (o 5305 corretamente não cria ordem)
— não é bug de contagem (achado B5).

**Follow-up B7 (edge, investigar com amostra):** se um 5301 legítimo chegar via STA com o corpo em
transporte compactado/UTF-16BE (como o CCS), a ingestão SISBAJUD passa pelo normalizador do
`cb391b47` (que é do StaInbound do CCS)? Se NÃO, os registros vêm mangleados → descartados → 5302 de
0 registros de um 5301 REAL. Confirmar se o caminho JUD normaliza o conteúdo.

## 4. Outros itens abertos / follow-ups

- **Dedup — risco RESIDUAL (auditoria A2/A3, verificado):** o fix mata o double-start (2 workers), mas
  o caminho de RETRY do worker legítimo sempre pede protocolo NOVO (`worker.ex:377-400,217`). Uma
  falha transitória DEPOIS dos bytes chegarem ao BACEN (timeout de resposta, conexão cortada), ou um
  403 visto pelo worker legítimo (`client.ex:2050-2052` → genérico `{:fail}`), ainda re-`request_protocol`
  → re-upload = protocolo duplicado. O incidente ESPECÍFICO (2 workers) está fechado; este é outro
  vetor. **Fix:** antes de re-pedir protocolo, consultar o estado remoto do protocolo já obtido
  (`Client.query_protocol`) e reusar/tratar como terminal se o conteúdo já subiu; mapear 403
  pós-upload p/ estado terminal, não retry cego.
- **A7 (pré-existente):** arquivo travado em `uploading` órfão (crash/restart) nunca é repescado
  (`get_pending_files`/`claim_pending` só pegam `pending`). Considerar watchdog (existe
  `SisbajudDeliveryWatchdog` no Core; ver equivalente no STA).

- **num_remessa por data de MOVIMENTO vs ENVIO:** o BACEN numera a remessa (NumRemessaArq) pela data
  de ENVIO; nós geramos por data do movimento. Em operação diária normal (movimento == envio no
  mesmo dia) coincide e correlaciona; quebra ao reenviar movimento antigo. O fix do #3.1 (ACCS002 =
  autoridade + fallback awaiting) contorna; a correlação por protocoloOrigem resolveria de vez.
- **DLQ em PROD:** `MONETARIE_DLQ` ~21 e `BACEN_DLQ` ~6 dead letters (threshold 10). Triar (podem
  ser resíduos dos ACCS002 antigos que davam `:invalid_xml`, ou dos envios duplicados). Verificar.
- **Correlação heurística (fallback por recência):** `unique_accs002_awaiter` só dispara com
  EXATAMENTE um ACCS001 aguardando (inequívoco). Seguro pós-dedup, mas o protocoloOrigem é o certo.
- **ACCS003/009 correlação por DtMovto** é best-effort (o XSD não ecoa a remessa do ACCS001) —
  sinalizado ALTO no código; precisa amostra real do BACEN.
- **UTF-16BE/zip no ENVIO (ACCS001):** o Core envia UTF-8 sem zip; a referência cecresa também.
  Os ACCS002 voltam, então o BACEN aceita o transporte — mas confirmar lendo o SitArq de um ACCS002
  que ANEXE (depende do fix #3.1).

## 5. Regras reforçadas nesta sessão (dono)

- **NUNCA permitir envio duplicado** (money-path regulatório). Feito (2 camadas), mas validar o
  edge do 403/retry na próxima (ver auditoria).
- **PARE de deployar em produção durante o teste do dono ao vivo.** A sessão ficou longa e houve
  muitos deploys seguidos em PROD. Próxima sessão: validar em bloco, deploy coordenado, não a cada fix.
- **Não inferir erros** — provar com log/dado real antes de afirmar.
- Deploy manual PROD sempre com o helper que VERIFICA a imagem antes de registrar.

## 6. Receita de deploy PROD (para retomar)

```
awsmon() { env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY -u AWS_SESSION_TOKEN \
  AWS_PROFILE=vulcimonetarie AWS_REGION=sa-east-1 AWS_EC2_METADATA_DISABLED=true aws "$@"; }
# build: docker buildx build --platform linux/arm64 -t <REG>/monetarie/<svc>:<TAG> --push <ctx>
#   core-api ctx=core/backend ; sta-api ctx=sta/backend
#   core-admin-ui: -f core/apps/admin/Dockerfile ctx=core
#   sta-admin-ui ctx=sta/admin-portal
# deploy: <scratchpad>/deploy_prod.sh <svc> monetarie/<svc> <TAG> <revisao-fonte-boa>
# validar: awsmon ecs wait services-stable ... + elbv2 describe-target-health
```
Consulta de estado em PROD via `awsmon ecs execute-command ... bin/monetarie rpc '...'` (funciona no
core-api; no sta-api o rpc não conecta e o eval não streama stdout — usar logs do CloudWatch).

## 7. COMANDO DE RETOMADA (copiar e colar na próxima sessão)

```
Retomar o trabalho da Monetarie. Leia PRIMEIRO, inteiro, o handoff
docs/handoff/2026-07-15-ccs-sta-envio-duplicado-accs002-handoff.md — ele tem o estado
verificado empiricamente (git, PROD, defeitos abertos com evidência arquivo:linha). NÃO
infira nada: confirme com log/dado real antes de afirmar.

Estado verificado (2026-07-15 noite): git main = 14e867fb (local == origin, working tree
limpo). PROD: core-api:51, core-admin-ui:22, sta-api:14, sta-admin-ui:8 (rollbacks no
handoff §1). JÁ RESOLVIDO E PROVADO: envio duplicado ao BACEN (sta-api:14, 1 upload por
envio), normalização do ACCS002 zip/UTF-16BE (parseia), "Falha de envio STA" grudada em
confirmado (+ dado do 202607080001 reparado). NÃO reabrir esses — estão certos.

TAREFA #1 (o defeito que RESTA, handoff §3.1): o ACCS002 não anexa porque o callback de
UPLOAD confirma o arquivo cedo demais — map_sta_to_ccs_status mapeia "success" -> "confirmed"
(sta_submission.ex:208-209). Aplicar o fix: o ACCS002 é a AUTORIDADE. (1) success/completed
-> não-terminal (fica "submitted"); (2) preservar sta_protocol numa transição "uploaded"
dedicada; (3) o confirm por ACCS002 NÃO pode zerar sta_protocol (payload sem protocolo);
reescrever os testes listados no §3.1 de sta_submission_test.exs. TDD. Avaliar correlação
por protocoloOrigem (precisa plumbing na cabine + amostra real do STA §8.1). VALIDAR em
local/HML; NÃO deployar em PROD enquanto o dono testa ao vivo — deploy coordenado, em bloco.

TAREFA #2 (handoff §3.2): SISBAJUD aba Arquivos com RESP "Gerado" 0 registros + cards
zerados no 15/07. Investigar com amostra/log REAL de uma remessa 5301 do dia antes de
afirmar causa (ver achados da auditoria no §7-anexo do handoff).

REGRAS DO DONO: não inferir/provar empiricamente; NUNCA permitir envio duplicado; NÃO
deployar/reiniciar pix/spb nem qualquer serviço em produção durante o dono operando/testando
money-path ao vivo; pt-br correto sem travessão; ao fechar frente, atualizar CLAUDE.md +
memória. Deploy manual PROD com o helper que VERIFICA a imagem antes de registrar (handoff §6).
```

## 8. Anexos — deep audit (2 agentes, read-only, evidência arquivo:linha)

As duas auditorias foram DOBRADAS neste doc com fatos verificados: o fluxo ACCS002 (§3.1, achados
D1-D6) e o dedup da cabine + SISBAJUD (§2.1, §3.2, §4, achados A1-A7 e B1-B7). Correções que fiz
sobre os relatos dos agentes (checadas com ferramenta): (a) o fix SISBAJUD `daeb9b92` ESTÁ em PROD
`core-api:51` (o agente supôs o contrário por memória velha do `:48`); (b) o RESP 0-reg é resíduo do
código velho (nasceu 18:48 BRT, 2h antes do fix às 20:50 BRT).

Aderência do CCS ao Guia BACEN (auditoria anterior desta sessão, no histórico do chat): núcleo do
ciclo diário aderente; abertos — grade horária ausente, CCS0005/ACCS004 sob demanda, estados
numéricos do STA não mapeados, UTF-16BE/zip no ENVIO (a referência cecresa também usa UTF-8); ECCS
48/48 mapeados.

## 9. DEEP AUDIT — veredito de regressão (o que a sessão mexeu está CERTO?)

- **Dedup da cabine (sta-api:14):** CORRETO para o incidente relatado (A1/A4). Risco residual A2/A3
  (retry pede protocolo novo) é OUTRO vetor, follow-up, não regressão.
- **Normalização ACCS002 (core-api):** CORRETA e provada viva (parseia zip/UTF-16BE).
- **Falha velha limpa (core-api:51):** CORRETA + dado reparado.
- **Fallback de correlação (core-api:51):** correto no que se propõe, mas NÃO resolve o caso real
  porque o callback de upload confirma antes (§3.1). Não é regressão (não piora nada); é insuficiente.
- **Parser SISBAJUD (daeb9b92, em PROD):** NÃO quebrou a geração do 5302 de um 5301 legítimo (B1).
- **Botões Atualizar / timeline / tela STA / login:** sem achados de regressão.
- **Suítes:** cabine outbound 42/0; CCS+handlers 276/0; vue-tsc admin 0 erros — todos verdes no
  commit `14e867fb`.
