# Handoff — `sta/onda2-resilient-transfer` (Onda 2.A) — PR #30 OPEN

**Data:** 2026-04-26 (Tasks 1-7 complete; PR #30 opened)
**Branch:** `sta/onda2-resilient-transfer` (worktree `/Users/luizpenha/monetarie/.worktrees/sta-paridade`)
**HEAD local:** `5baad8df` — `docs(sta): handoff update — Onda 2.A Tasks 1-6 complete`
**Branch pushada?** ✅ pushed to `origin/sta/onda2-resilient-transfer`
**PR:** https://github.com/FluxiqBR/monetarie/pull/30 — OPEN, awaiting review/merge
**Commits ahead of origin/main:** 9 (2 prep docs + 6 feat + 1 docs handoff)
**Plan canônico (raiz Onda 1-4):** `sta/docs/plans/2026-04-26-sta-conformance-rollout.md`
**Plan Onda 2.A (esta sub-PR):** `sta/docs/plans/2026-04-26-sta-onda2a-resilient-transfer.md`

## Implementation summary (commits in order)

| Task | Commit | Files | Tests added |
|---|---|---|---|
| 1 — Client.posicao_upload + RangesRecebidos parser | `34104351` | client.ex, client_behaviour.ex, types.ex, client_test.exs | 6 (Bypass) |
| 2 — Client.upload_part + Content-Range | `31283d37` | client.ex, client_behaviour.ex, client_test.exs | 6 (Bypass) |
| 3 — Client.download_part + ETag/X-Content-Hash | `15dbf667` | client.ex, client_behaviour.ex, types.ex, client_test.exs | 8 (Bypass) |
| 4 — outbound_files migration (etag, last_modified, bytes_uploaded) | `9f214a2e` | migration, outbound_file.ex, outbound_file_test.exs (new) | 7 (changeset) |
| 5 — Operations.upload_with_retry chunked + reconciliation | `d00c965c` | operations.ex, operations_test.exs | 3 (Bypass) |
| 6 — Operations.download_with_retry + integrity verify | `ce29550d` | client.ex (parser), operations.ex, operations_test.exs | 3 (Bypass) |

**Total delta:** 11 files, +2413 / -33 LOC. **33 new test cases.**

## Touchstones (all green)

- `mix compile` — 5 baseline warnings unchanged
- `mix test test/sta_connector/sta/client_test.exs` — 55 tests, 6 baseline failures (Soap.parse_response)
- `mix test test/sta_connector/sta/operations_test.exs` — 43 tests, 0 failures
- `mix test test/sta_connector/files/outbound_file_test.exs` — 7 tests, 0 failures
- `mix test test/sta_connector/application_test.exs` — 3 tests, 0 failures
- `mix ecto.migrate` (dev DB on port 15432) — applied cleanly
- Guard rail: `git diff $(git merge-base origin/main HEAD)..HEAD --name-only | grep -v '^sta/'` returns empty
- `mix format` clean for all touched files

## Task 7 (push + PR) checklist for next session

```bash
cd /Users/luizpenha/monetarie/.worktrees/sta-paridade

# 1. Verify state still matches
git status                                          # On sta/onda2-resilient-transfer, clean
git log --oneline -8                                # ce29550d at top
git rev-list --count origin/main..HEAD              # 8

# 2. Re-run touchstones
cd sta/backend
mix compile 2>&1 | grep -c "warning:"                # 5
mix test test/sta_connector/sta/client_test.exs test/sta_connector/sta/operations_test.exs test/sta_connector/files/outbound_file_test.exs test/sta_connector/application_test.exs 2>&1 | tail -3
# Expected: 108 tests, 6 failures

# 3. Push (NEEDS EXPLICIT USER AUTHORIZATION)
git push -u origin sta/onda2-resilient-transfer

# 4. Open PR (gh pr create with body referencing this plan + Task 0 misdiagnoses + new findings)
```

## Deferred follow-ups (small, NOT in critical path)

These were intentionally deferred to keep each Task atomic; they're orthogonal to the chunked-upload + ranged-download + ETag plumbing already shipped.

1. **Worker.ex `:on_chunk_progress` wiring (~1h).** `outbound/worker.ex` currently calls `Client.upload_file/2` directly (single PUT, ignoring the chunked path added in Task 5). Wire it to call `Operations.upload_with_retry/4` with a `:chunk_threshold` from config + an `:on_chunk_progress` callback that updates `OutboundFile.bytes_uploaded` after each chunk. The DB column shipped in Task 4 + the callback hook shipped in Task 5 are both already in place — this is purely a wiring task.

2. **Ranged-resume on transient mid-download failure (~2-3h).** `Operations.download_with_retry/2` (Task 6) currently does a single full GET. The mid-stream resume path — re-fetch via `Client.download_part/2` with `Range: bytes=N-{end}` + `If-Match: <etag>` from the prior attempt — was deferred. Manual §6.4 specifies `bytes={start}-{end}` (both bounds), so the open-ended `bytes=N-` form is unverified; would need either an explicit `expected_size` opt or a Manual re-read for §6.4 boundary edge cases. Task 4 columns + Task 6 etag/last_modified plumbing already capture the needed state.

3. **`Operations.resume_upload/1` (~1h).** Mentioned in the parent plan but never elaborated; intent was probably "resume an interrupted chunked upload after a worker restart by re-reading `OutboundFile.bytes_uploaded` and calling `posicao_upload` to reconcile, then resuming missing chunks". The reconciliation logic from Task 5 already supports this; just needs a public entry point.

These follow-ups are small enough to ship as separate sub-PRs after Onda 2.A merges, OR as additional commits on this branch before opening the PR — operator decision.

---

## TL;DR

Onda 1 está merged em `main` (PR #24, commit `301503ec`). Esta sessão começou Onda 2.A no worktree existente: criou branch `sta/onda2-resilient-transfer` cortada de `main`, executou **Task 0 — Manual Verification** (mandatory per Onda 1's Brainstorm 2 Pattern), e escreveu um bite-sized plan informado pelos findings.

Task 0 leu Manual STA v1.5 §2.6, 5.3-5.6, 6.1-6.4, 7, 8, e Tabela 3 — verificou 16 claims, **catou 4 misdiagnoses** no plano original, e descobriu **5 findings novos** (incluindo um elemento XML diferente do que o plano dizia + um header de integridade BCB-specific + um HTTP 501 ausente do plano).

Próxima sessão começa em **Task 1 do plano Onda 2.A** (Types.PosicaoUpload + Client.posicao_upload/1 com parser de `<RangesRecebidos>`). 7 tasks total, ~3-4 dias estimados → **provavelmente cabe em 2-3 sessões focadas**.

---

## Estado concreto

| Item | Valor |
|---|---|
| Branch local | `sta/onda2-resilient-transfer` (1 commit ahead de `origin/main`) |
| HEAD local | `5bae4f12` |
| origin/main | `301503ec` (= merge commit do PR #24 Onda 1) |
| Working tree | clean |
| postgres dev container | provavelmente UP (foi UP durante Onda 1; verificar antes de migration de Task 4) |
| ICP-Brasil chain | instalada (Onda 1, em `sta/backend/priv/certs/icp_brasil_ca_chain.pem`) |

---

## Task 0 — Manual Verification: findings (resumo)

**Detalhes completos** em `sta/docs/plans/2026-04-26-sta-onda2a-resilient-transfer.md#task-0`. Resumo:

### ✅ Verified (16 claims)

Endpoints (`PUT/GET .../conteudo`, `GET .../posicaoupload`); `Content-Range`/`Range`/`If-Match`/`If-Unmodified-Since` semântica; `ETag`+`Last-Modified` response headers; HTTP 412/416 erros; transferência paralela permitida (§5.5+§6.3); BCB connection limits (10 simultâneos / 120/min query, §2.6).

### 🚨 Misdiagnoses caught (4)

1. **`<UltimoByteRecebido>` NÃO EXISTE.** Elemento real é `<RangesRecebidos>` com formato `"0-3;5-8"` — lista semicolon-separated de ranges com hífen. **Major design impact:** chunked upload pode ter GAPS; client precisa reconciliar ranges arbitrários, não só retomar de byte N.
2. **HTTP 410 em upload context = "cancelado pelo Banco"** (NÃO "expired" como Onda 2.C plan implied).
3. **HTTP 429 + Retry-After é study-conjecture.** Manual §2.6 estabelece os limits mas NÃO documenta response code do rate limit.
4. **Threshold 1 MB é study-conjecture.** Manual não impõe.

### 🆕 New findings (5)

1. `<RangesRecebidos>` parser novo precisa ser escrito — não usar parser linha-única simples.
2. `<Situacao>` enum em `posicaoupload` (`Transmissão não iniciada` / `Transmissão pendente` / `Transmissão finalizada`) é **distinto** de `<EstadoAtual><Codigo>` usado em queries (conceitos ortogonais: upload-side vs BCB-processing-side).
3. `X-Content-Hash: SHA-256 <hex>` response header em downloads — **BCB non-standard**, fonte canônica de integrity check.
4. HTTP **501** "O range multipart não é suportado" — defensive: restart do 0 com single range.
5. `Content-Type` MUST NOT estar nos headers de `posicaoupload`, `download_full`, `download_part` — **audit do client existente é necessário** (Task 1 ou inline em cada Task que tocar esses endpoints).

### ⏸️ Operator confirm (2)

- Concurrency budget split entre upload/download dentro do limite de 10 simultâneos (Manual não especifica)
- Default chunk size (1 MB? 5 MB? — picking a working default for Onda 2.A; revisitar depois do primeiro real BCB smoke)

---

## Plano de execução — Tasks 1-7 (HISTÓRICO — Tasks 1-6 completas em `34104351`..`ce29550d`)

Estado original deste plano (preservado para referência); **status atualizado** no topo do doc.

| # | Task | Estimate | Outcome |
|---|---|---|---|
| 1 | `Types.PosicaoUpload` + `Client.posicao_upload/1` + `<RangesRecebidos>` parser | 4-6h | ✅ `34104351` (~1h actual) |
| 2 | `Client.upload_part/2` (PUT + Content-Range) | 3-4h | ✅ `31283d37` (~30min actual) |
| 3 | `Client.download_part/2` + ETag/`X-Content-Hash` plumbing | 4-6h | ✅ `15dbf667` (~1h actual) |
| 4 | Migration: `OutboundFile` extends with `etag`, `last_modified`, `bytes_uploaded` | 2-3h | ✅ `9f214a2e` (~30min actual) |
| 5 | `Operations.upload_file/3` learns chunked upload + range reconciliation | 6-8h | ✅ `d00c965c` (~1.5h actual) — worker.ex wiring deferred (see follow-ups) |
| 6 | `Operations.download_file/2` learns ranged GET + ETag persistence + 412/416/501 handling | 6-8h | ✅ `ce29550d` (~1h actual) — ranged-resume mid-stream deferred (see follow-ups) |
| 7 | Final validation + PR (mirror Onda 1 Task 6 closeout) | 2-3h | ⏳ Pending push authorization |

**Plan estimate vs actual:** plan called for 27-38h; actual implementation took ~5-6h thanks to a uniform pattern across Tasks 1-3 (Bypass+Client+Manual-citation comments) and Tasks 5-6 (Operations+Bypass).

**Total:** ~27-38h. Em sessão de 4-5h focada, dá pra fechar Tasks 1-3 facilmente; Tasks 4-6 ficam pra sessão seguinte; Task 7 numa terceira (ou final da segunda).

**Sequência recomendada:** Tasks 1+2+3 em paralelo (independentes — todos client-level), depois Task 4 (migration), depois Tasks 5+6 (operations-level com dependencies), depois Task 7.

---

## Constraints / governança

- **Esta sessão SÓ pode tocar `sta/*`** (4 outras sessões em paralelo no mesmo monorepo). NÃO modificar `core/*`, `pix/*`, `spb/*`, `npc/*`, `clst/*`, `backoffice/*`, `mobile/*`, `infra/*`, `docs/` (raiz), `MEMORY.md` (raiz), `CLAUDE.md` (raiz). Apenas `sta/docs/*` é permitido.
- **Pattern: Manual Verification ainda aplica.** Se durante implementação surgir nova claim do plano que não foi tagged em Task 0 (ex.: detalhes de error XML body), **abrir `sta/Manual_STA_Web_Services.pdf` e verificar antes de codar**. Documentar no commit message ou inline comment.
- **`mix format` SÓ em arquivos tocados pela sub-PR** — baseline drift de `sta/backend/` (70+ files) é out-of-scope (anotação documentada na Onda 1 PR #24 body).
- **`mix test` full-suite tem 2 compile errors pré-existentes** (`channels_test.exs:90`, `files_channel_test.exs:94` — `assert_reply _, :ok, _` syntax inválida). Skip-around: testar arquivos específicos, não a suite completa.
- **NÃO push pro origin nem abra PR sem confirmação explícita** (consistência com Onda 1).
- **Worktree fica neste branch** até PR ser merged; depois cortar nova branch para Onda 2.B/C/D off `main` post-merge.

---

## Decisão policy quando algo for ambíguo

1. **Se claim do plano vs realidade do código diverge:** confiar no código, atualizar plano (precedente: Onda 1 Brainstorm 2).
2. **Se claim do plano vs Manual diverge:** confiar no Manual, fix-commit + documentar como misdiagnosis (precedente: 7 misdiagnoses caught entre Onda 1 + Task 0).
3. **Se Manual silência sobre algo:** marcar como `[STUDY-CONJECTURE]`, escolher default sensato + env-tunable, documentar em código + commit message + PR body.
4. **Se Manual ambíguo (duas leituras possíveis):** marcar como `[NEEDS-OPERATOR-CONFIRM]`, escolher leitura conservadora, **flag explícito no PR body** pedindo confirmação humana antes de merge.

---

## Referências críticas

- **Manual canônico:** `sta/Manual_STA_Web_Services.pdf` (1.832 linhas via `pdftotext -layout`)
- **Plan Onda 2.A:** `sta/docs/plans/2026-04-26-sta-onda2a-resilient-transfer.md`
- **Plan rollout (Onda 1-4):** `sta/docs/plans/2026-04-26-sta-conformance-rollout.md` — em particular `#pattern-manual-verification`
- **Onda 1 final handoff:** `sta/docs/handoff/2026-04-26-sta-paridade-onda1-final-handoff.md` (lessons learned, infra state)
- **Onda 1 PR (merged):** https://github.com/FluxiqBR/monetarie/pull/24
- **Misdiagnosis pattern precedent:** Onda 1 Brainstorm 2 (3 misdiagnoses) + this session Task 0 (4 misdiagnoses) — total 7 caught. **Confiança baixa em wire-format claims do estudo; confiança alta no Manual lido diretamente.**

---

## Heuristic — quando parar a sessão

- Após cada Task commit landed: validar `mix compile` + `mix test` no arquivo tocado.
- Se Tasks 1+2+3 fechadas em uma sessão: bom ponto de pausa (lib-level work + parser, sem mudança DB).
- Se entrar em Task 4 (migration): **NÃO pausar mid-migration** — finish ou revert.
- Se entrar em Task 5/6 (operations): **pausa OK após qualquer commit completo**, mas atualizar este handoff antes de pausar.
