fix: admin/api apontava para admin/code (inexistente) — crash no app

Ao dividir _shared.py em admin/api/*.py ontem, o cálculo
`Path(__file__).resolve().parent.parent / "code"` foi copiado sem ajustar
para o nível de diretório novo. No arquivo original (admin/models_api.py,
direto em admin/) dois `.parent` chegavam na raiz do repo. Em
admin/api/shared.py, um nível mais fundo, dois `.parent` param em admin/ —
e admin/code nunca existiu. sys.path nunca recebia code/, então toda ação
que passa por `server` (analisar voz, aplicar decisões) crashava o app com
ModuleNotFoundError: server_tools.

O bug sobreviveu a duas rodadas de validação da sessão anterior — lint
zero, 1454 testes verdes, comando testado manualmente pela ponte — porque
todos rodam num venv com install editável (__editable__.fcp_mcp_server.pth)
que já deixa fcpxml/server_tools importáveis por conta própria, mascarando
qualquer erro no cálculo manual de sys.path. Só o app real, no fallback sem
uv, expõe o bug.

Correção: o cálculo de sys.path sai de cada módulo de comando (estava
duplicado em nove arquivos) e passa a existir uma única vez em
admin/api/__init__.py, que roda antes de qualquer submódulo — nenhum
precisa mais da própria cópia.

O teste de regressão precisou de duas tentativas pelo mesmo motivo do bug:
a primeira versão também passava com o bug presente, por rodar no mesmo
venv "de sorte". Só ficou confiável isolando um subprocess que remove
site-packages do sys.path antes de importar — confirmado nos dois sentidos,
falha com o bug reintroduzido e passa com a correção
(TestCodeDirResolution).

Detalhe completo, incluindo por que o comando manual não pegou:
Engine/docs/05_EXPERIENCIAS.md #25.

Lint zerado, 1457 testes passando (3 novos), app compilado.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
João Henrique
2026-08-20 09:50:04 -04:00
co-authored by Claude Opus 5
parent cbd9297751
commit 711c397dfe
14 changed files with 169 additions and 222 deletions
+53
View File
@@ -42,6 +42,7 @@ que merece entrada.
| 22 | 2026-08-19 | `VideoPlayer` (AVKit) aborta em runtime no app compilado por `swiftc` — etapa 5 fechava o app; trocado por `AVPlayerLayer` | `resolvido` |
| 23 | 2026-08-19 | Dividir `writer.py` em pacote quebrou `@patch('fcpxml.writer.subprocess')` — a suíte protege comportamento, não localização | `resolvido` |
| 24 | 2026-08-19 | `admin/test_models_api.py` existia mas estava fora de `testpaths` — 13 testes que nunca rodaram | `resolvido` |
| 25 | 2026-08-20 | `admin/api/shared.py` apontava para `admin/code` (inexistente) após a divisão — install editável mascarou o bug em toda validação anterior | `resolvido` |
> Mantenha o índice acima sempre sincronizado com as entradas mais recentes.
@@ -1313,3 +1314,55 @@ o outro; percentil entrega um punhado útil nos dois casos.
rodando. Vale também para o lint: `admin/` ainda não é coberto pelo
`run_after_fix.sh`, que roda só dentro de `code/`.
- **Estado:** `resolvido`
---
## 25 — 2026-08-20 — `admin/api/shared.py` apontava para `admin/code` (inexistente)
- **Sintoma:** app do usuário crashava em toda ação que passa por `server`
(ex: "Analisar voz"), com `ModuleNotFoundError: No module named
'server_tools'`. Sobreviveu a **duas rodadas de validação minha** na sessão
anterior — lint zero, 1454 testes verdes, comando testado manualmente pela
ponte — sem nenhuma delas pegar o bug.
- **Causa raiz:** ao dividir `admin/_shared.py` (#25 da sessão de refatoração,
commit `ffaebb3`) em `admin/api/*.py`, o cálculo
`Path(__file__).resolve().parent.parent / "code"` foi copiado sem ajuste.
No arquivo original (`admin/models_api.py`, direto em `admin/`), dois
`.parent` chegam na raiz do repo. Em `admin/api/shared.py`, um nível mais
fundo, dois `.parent` param em `admin/` — e `admin/code` nunca existiu.
`sys.path` nunca recebia `code/`, então `import server_tools` (que só
funciona com `code/` no path) falhava assim que qualquer handler tentava
`from server import ...`.
- **Por que passou pela validação anterior:** todo teste que exercitava esse
caminho importava `admin.api.*` **dentro do processo do pytest**, que já
roda com `cwd=code/` sob um venv com **install editável**
(`__editable__.fcp_mcp_server*.pth`) — isso já deixa `fcpxml`/`server_tools`
importáveis por conta própria, mascarando qualquer erro no cálculo manual
de `sys.path`. O teste manual pela ponte (`uv run python
admin/models_api.py analyze_voice ...`) tem o mesmo problema: `uv run`
ativa o mesmo venv com o mesmo install editável. **Só o app real, chamando
o fallback `python3` sem `uv` ou um venv sem o install editável, expõe o
bug** — que é exatamente a diferença entre o ambiente de teste e o do
usuário.
- **Solução adotada:** o cálculo de `sys.path` saiu de cada módulo de
comando e passou a existir **uma única vez**, em `admin/api/__init__.py`
— que roda antes de qualquer submódulo do pacote, então nenhum deles
precisa da própria cópia. `.parent.parent.parent` (três níveis: `api/` →
`admin/` → raiz → `code/`).
- **Como o teste de regressão foi validado (e por que precisou de duas
tentativas):** a primeira versão do teste também passava com o bug
presente, pelo mesmo motivo do parágrafo acima — rodava em processo com o
install editável ativo. Só ficou confiável rodando um `subprocess` limpo
que remove manualmente qualquer entrada `site-packages` de `sys.path`
antes de importar, isolando o mecanismo real que o `__init__.py` precisa
fornecer. Confirmado nos dois sentidos: falha com o bug reintroduzido,
passa com a correção (`tests/test_models_api.py::TestCodeDirResolution`).
- **Aprendizado:** um install editável no venv de teste é uma segunda fonte
de verdade que mascara bugs de `sys.path` — o mesmo defeito de "a suíte
passa mas o comportamento real não bate" da entrada #23, só que desta vez
nem *rodar o comando manualmente* pegou, porque o `uv run` usado para
testar caía no mesmo venv "de sorte" que o app não usa. Ao validar correção
de caminho/import, rodar num ambiente que não tenha as dependências
instaladas por fora do mecanismo sendo testado — ou o teste prova que o
ambiente de teste está bem configurado, não que o código está certo.
- **Estado:** `resolvido`