Skip to content

fix(pairs): POST /operated idempotente (409) + semeia env na 1ª adição (M2+M3)#114

Merged
danzeroum merged 1 commit into
masterfrom
claude/criptotrade-onboarding-06pd5p
Jul 21, 2026
Merged

fix(pairs): POST /operated idempotente (409) + semeia env na 1ª adição (M2+M3)#114
danzeroum merged 1 commit into
masterfrom
claude/criptotrade-onboarding-06pd5p

Conversation

@danzeroum

Copy link
Copy Markdown
Owner

M2 + M3 · Write de pares operados: idempotência (409) + semeadura do env na 1ª adição

Dois bugs de backend no write de pares operados, revelados pelo uso real na VPS. Diff só backend + testes (src/api/routes/pairs.py + tests/api/test_operated_pairs.py).

M2 — POST /v1/pairs/operated não era idempotente

Adicionar um par já operado retornava 201 de novo (ETH/USDT 2×). A tabela tem symbol PRIMARY KEY e o store usa INSERT OR IGNORE (nunca duplica de fato), mas o contrato HTTP estava errado.
Fix: checar contra o conjunto efetivo operated_pairs() (não só a tabela crua) — cobre o duplicado na tabela E o par já operado via fallback do env — e retornar 409 already_operated (idioma 4xx da casa: HTTPException(detail={error,message})).

M3 — armadilha da precedência DB>env na 1ª adição

Tabela vazia + env com pares → o console mostrava BTC operando (fallback env); ao adicionar o 1º par pela UI a tabela ficava não-vazia e os pares do env caíam em silêncio (BTC sumia).
Fix: quando a tabela crua está vazia (store.symbols(), não operated_pairs() — que nunca é vazio por causa do default; esse é o ponto sutil que faz a semeadura disparar só na transição env→DB), semear o conjunto efetivo do env antes de inserir o novo par → o add vira incremental, preservando a intenção do operador.

Auditoria honesta

before = operated_pairs() (efetivo) → o diff config_changed vira ["BTC/USDT"]→["BTC/USDT","ETH/USDT"] (migração env→DB documentada), não []→["ETH/USDT"] (que escondia o BTC sumindo).

Sem surpresa no loop em execução

Semeia os mesmos símbolos do env que o loop já roda; o novo par só vale no próximo restart, como documentado (N8²).

Testes (fixture env com SYMBOLS=BTC/USDT)

  • test_add_validates_and_persists atualizado (BTC agora semeado → {BTC,ETH}).
  • +test_add_is_idempotent_409 (dup na tabela → 409, sem duplicata).
  • +test_add_already_effective_via_env_is_409 (par do env → 409, sem semeadura).
  • +test_first_add_seeds_env_pairs (reproduz/corrige o incidente do BTC).
  • +test_first_add_audit_diff_is_incremental (diff honesto na auditoria).
  • test_db_wins_over_env intacto (é store-level, não passa pela rota).

Validação

  • pytest completo verde, cobertura 78.13% ≥ 72%.

🤖 Generated with Claude Code


Generated by Claude Code

…o (M2+M3)

Dois bugs no write de pares operados, revelados pelo uso real na VPS.

M2 — POST /v1/pairs/operated não era idempotente: adicionar um par já operado
retornava 201 de novo (ETH/USDT 2×). A tabela tem symbol PRIMARY KEY e o store usa
INSERT OR IGNORE (nunca duplica de fato), mas o contrato HTTP estava errado. Fix:
checar contra o conjunto EFETIVO operated_pairs() (não só a tabela crua) — cobre o
duplicado na tabela E o par já operado via fallback do env — e retornar 409
already_operated (idioma 4xx da casa: HTTPException detail={error,message}).

M3 — armadilha da precedência DB>env na 1ª adição: com a tabela vazia + env com
pares, o console mostrava BTC operando (fallback env); ao adicionar o 1º par pela UI
a tabela ficava não-vazia e os pares do env caíam em silêncio (BTC sumia). Fix:
quando a tabela CRUA está vazia (store.symbols(), não operated_pairs() — que nunca é
vazio por causa do default), semear o conjunto efetivo do env ANTES de inserir o
novo par, tornando o add incremental e preservando a intenção do operador.

Auditoria honesta: before = operated_pairs() (efetivo) → o diff config_changed vira
["BTC/USDT"]→["BTC/USDT","ETH/USDT"] (migração env→DB documentada), não []→["ETH"].

Sem surpresa no loop em execução: semeia os mesmos símbolos do env que o loop já
roda; o novo par só vale no próximo restart, como documentado (N8²).

Testes (tests/api/test_operated_pairs.py, fixture env com SYMBOLS=BTC/USDT):
- test_add_validates_and_persists atualizado (BTC agora semeado → {BTC,ETH}).
- +test_add_is_idempotent_409 (dup na tabela → 409, sem duplicata).
- +test_add_already_effective_via_env_is_409 (par do env → 409, sem semeadura).
- +test_first_add_seeds_env_pairs (reproduz/corrige o incidente do BTC).
- +test_first_add_audit_diff_is_incremental (diff honesto na auditoria).
- test_db_wins_over_env intacto (é store-level, não passa pela rota).

Validação: pytest completo verde, cobertura 78.13% ≥ 72%. Só backend + testes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UZ3vLNTHKekjQVtjFVRi5D
@danzeroum
danzeroum merged commit ca6b705 into master Jul 21, 2026
12 checks passed
danzeroum pushed a commit that referenced this pull request Jul 21, 2026
…ona (M4)

reset_paper_state.py estava "inoperável como documentado" na VPS. A exploração
mostrou que o fix é menor que o descrito: --yes JÁ existe e o docstring JÁ usa
`python -m`. Os tropeços reais eram dois:

1. EOFError: sem --yes e sem TTY (`docker compose exec` sem -it), o input() do prompt
   estourava um traceback. Fix: try/except EOFError em main() → aborta limpo
   (return 1, sem traceback) com mensagem apontando --yes.

2. Doc de operação: o fluxo documentado era `stop orchestrator → python -m … → start`,
   mas (a) rodar `python -m` no HOST não tem o ambiente (é dockerizado) e (b)
   `docker compose exec orchestrator` falharia — exec exige container DE PÉ e o
   fluxo para o orchestrator. Fix: a linha não-interativa passa a mirar o container
   `app` (fica de pé, compartilha o mesmo volume ./data / LEDGER_DIR que o
   orchestrator — confirmado no docker-compose.vps.yml), com --yes (sem TTY):
       docker compose -f docker-compose.vps.yml stop orchestrator
       docker compose -f docker-compose.vps.yml exec app python -m scripts.reset_paper_state --yes
       docker compose -f docker-compose.vps.yml start orchestrator
   Reforçado também que a invocação é SEMPRE `python -m` (rodar `python
   scripts/reset_paper_state.py` daria ModuleNotFoundError: src).

NÃO adicionei bootstrap de sys.path: bateria no gate E402 do ruff (select E4) e
fugiria da convenção `python -m` de todos os scripts do repo.

Testes (tests/test_reset_paper_state.py — antes só cobriam a função pura, não o
main() CLI; LEDGER_DIR isola ledger E app db via get_db_path):
- +test_main_yes_resets_without_prompting (--yes pula o prompt e reseta; input()
  não é chamado).
- +test_main_eof_without_yes_aborts_cleanly (EOFError → return 1, sem traceback,
  estado intacto).

Validação: pytest completo verde, cobertura 78.12% ≥ 72%; ruff (gate) limpo.

Fecha a faixa de manutenção M1–M4 (M1 #113, M2+M3 #114, M4 este PR).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UZ3vLNTHKekjQVtjFVRi5D
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants