fix(pairs): POST /operated idempotente (409) + semeia env na 1ª adição (M2+M3)#114
Merged
Merged
Conversation
…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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/operatednão era idempotenteAdicionar um par já operado retornava 201 de novo (ETH/USDT 2×). A tabela tem
symbol PRIMARY KEYe o store usaINSERT 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 409already_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ãooperated_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 diffconfig_changedvira["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
envcomSYMBOLS=BTC/USDT)test_add_validates_and_persistsatualizado (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_envintacto (é store-level, não passa pela rota).Validação
🤖 Generated with Claude Code
Generated by Claude Code