diff --git a/docs/postmortems/2026-09-24-busca-por-tema-indisponivel.md b/docs/postmortems/2026-09-24-busca-por-tema-indisponivel.md index 8e494c47..7fb1915b 100644 --- a/docs/postmortems/2026-09-24-busca-por-tema-indisponivel.md +++ b/docs/postmortems/2026-09-24-busca-por-tema-indisponivel.md @@ -15,15 +15,15 @@ ## Timeline -| Momento | Data/hora | Evento | -| ------------------------ | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Início provável (ops) | 2026-08-17/18 (OPS51/OPS52) | O cutover Vercel → homeserver moveu o app para o compose do homeserver; o `DEEPSEEK_API_KEY` (que na era Vercel vivia no project env) não entrou no `~/stack/teqo-1313.env` — ficou só no `~/stack/.env`, que o compose do app não lê. As capacidades de IA passaram a degradar em 100%. | -| Início provável (código) | 2026-09-18 (`45eef25b`, C192) | A busca por tema nasce chamando `generateObject` do `deepseek-flash` com `maxOutputTokens: 200` e thinking default; S28 (2026-09-23) reusa o mecanismo e C219 (2026-09-24, `38e67426`) adiciona o 3º corpus — mesma config. Os tetos irmãos (120/400/500) nasceram junto dos seus donos. | -| Detecção | 2026-09-24 (sessão `fix/11`) | Humano relata "A busca por tema esta indisponivel." — o aviso do modo `tema` no acervo interno (`/campanha/comunicacao/acervo`). | -| Diagnóstico | 2026-09-24 | Read-only em prod: containers `teqo-1313`/`teqo-staging` **sem** `DEEPSEEK_API_KEY`/`DEEPINFRA_API_KEY`/`RESEND_API_KEY`/`VAPID_*` no env (nomes via `docker inspect`); `~/stack/.env` **tem** `DEEPSEEK_API_KEY` + `DEEPINFRA_API_KEY`; os env files do app não. Smoke real do módulo com a chave da estação: 5/10 falhas com `AI_NoObjectGeneratedError` (JSON truncado / resposta vazia) no teto 200; `usage` medido: 93–354 tokens de raciocínio contra 28–34 de texto. Com `thinking: disabled`: 6/6. | -| Correção mergeada | a definir (PR #1333) | — | -| Deploy | a definir | deploy começa no merge; produção só após approve humano no environment `production` | -| Verificado em prod | pendente (aguarda o humano) | pendente — e só vale depois de a chave entrar no `~/stack/teqo-1313.env` e o container ser recriado | +| Momento | Data/hora | Evento | +| ------------------------ | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Início provável (ops) | 2026-08-17/18 (OPS51/OPS52) | O cutover Vercel → homeserver moveu o app para o compose do homeserver; o `DEEPSEEK_API_KEY` (que na era Vercel vivia no project env) não entrou no `~/stack/teqo-1313.env` — ficou só no `~/stack/.env`, que o compose do app não lê. As capacidades de IA passaram a degradar em 100%. | +| Início provável (código) | 2026-09-18 (`45eef25b`, C192) | A busca por tema nasce chamando `generateObject` do `deepseek-flash` com `maxOutputTokens: 200` e thinking default; S28 (2026-09-23) reusa o mecanismo e C219 (2026-09-24, `38e67426`) adiciona o 3º corpus — mesma config. Os tetos irmãos (120/400/500) nasceram junto dos seus donos. | +| Detecção | 2026-09-24 (sessão `fix/11`) | Humano relata "A busca por tema esta indisponivel." — o aviso do modo `tema` no acervo interno (`/campanha/comunicacao/acervo`). | +| Diagnóstico | 2026-09-24 | Read-only em prod: containers `teqo-1313`/`teqo-staging` **sem** `DEEPSEEK_API_KEY`/`DEEPINFRA_API_KEY`/`RESEND_API_KEY`/`VAPID_*` no env (nomes via `docker inspect`); `~/stack/.env` **tem** `DEEPSEEK_API_KEY` + `DEEPINFRA_API_KEY`; os env files do app não. Smoke real do módulo com a chave da estação: 5/10 falhas com `AI_NoObjectGeneratedError` (JSON truncado / resposta vazia) no teto 200; `usage` medido: 93–354 tokens de raciocínio contra 28–34 de texto. Com `thinking: disabled`: 6/6. | +| Correção mergeada | 2026-09-25 02:09Z (`3cdd1cd8`) | PR #1333 mergeado com o required check `CI (PR) / checks` verde (auto-merge rebase; o fix é o commit `4e7b8444`). | +| Deploy | 2026-09-25 02:39–03:32Z | Run `36087193017` (requeue do merge): verify full verde → `deploy-staging` success → `deploy-production` aprovado pelo humano; `teqo-1313` recriado em `3cdd1cd8` às 03:32:43Z. Staging e produção rodam o fix **com** as duas chaves no runtime (verificado por `docker inspect`, só nomes). | +| Verificado em prod | pendente | **Bloqueado por rede:** a rota do homeserver até `api.deepseek.com` está intermitente (abaixo) — o fix e a chave estão no ar, mas a feature ainda pode degradar. | ## O bug @@ -55,6 +55,11 @@ Nenhuma migration, nenhum Consent/access, nenhum contrato de URL/shape público. - Guard de ops: `bash -n` verde; `deployScript.unit.spec.ts` verde (22) pinando o audit e a ordem (healthcheck → audit → smoke); audit exercitado com `docker` stubado (env com buraco → `::warning::` com a lista exata; tudo presente → silêncio). - CI: o required check `CI (PR) / checks` do PR #1333 (o auto-merge nativo só mergeia com ele verde). - Prod: pendente — deploy começa no merge; produção só após approve humano; a validação do humano depende de a chave entrar no `~/stack/teqo-1313.env` (copia-e-cola do runbook) e o container ser recriado. +- Prod (2026-09-25): o deploy saiu — produção roda `3cdd1cd8` (o fix) com as duas chaves no runtime, mas a verificação funcional esbarrou numa **terceira camada, de rede** (abaixo). O aviso de indisponibilidade segue possível em parte das tentativas. + +### Rota intermitente até o provedor (2026-09-25) + +A rota do homeserver até `api.deepseek.com` (CloudFront `d3bbv8sr76az5s.cloudfront.net` → `3.173.21.63:443`) está degradada: 3 `curl` do **host** deram 2 timeouts de 8s e 1 `401` em 0,55s; de dentro do **container de produção**, o `GET /models` deu 2 `UND_ERR_CONNECT_TIMEOUT` e 1 `200`. Não é bloqueio geral de rede: `example.com` responde 200 no container (3/3), `aws.amazon.com`/`d1.awsstatic.com` respondem no host, `api.deepinfra.com` responde 200 em 0,5s e a estação (egress AWS) alcança `api.deepseek.com` em 1,1s. Também não é o egress do Docker: o primeiro teste de `example.com` deu timeout numa janela ruim e foi refutado em 3/3 na repetição. A falha de rede cai no mesmo `catch` → `null` → aviso; **sem estabilizar a rota (ou trocar/rotear o provedor), a busca por tema continuará intermitente**. ## Prevenção @@ -66,6 +71,7 @@ Nenhuma migration, nenhum Consent/access, nenhum contrato de URL/shape público. | Falhar o deploy quando uma feature env esperada está ausente | cara | documentada — muda contrato de ops (pode travar hotfix/rollback) e é decisão humana | | Observabilidade de degradação (contador/alerta por `catch`→`null` de cada feature, em vez de só o aviso na UI) | cara | documentada — instrumentação nova | | Passar as chaves de `~/stack/.env` ao runtime via `environment:` no compose (infra-solla), em vez de copiar linhas | cara | documentada — toca repo de infra fora deste repo | +| Estabilizar a rota até o provedor LLM (egress alternativo/proxy pela rede saudável, retry+timeout no cliente, ou trocar para Deep Infra — alcançável em 0,5s) | cara | documentada — aguarda decisão humana (produto/custo/infra) | **Estratégia implementada:** os cinco pins de regressão travam a configuração do provedor em cada call site (o defeito não volta sem um teste vermelho), o dono único impede que um irmão fique para trás num próximo ajuste, e o audit de deploy transforma "capacidade degradada em silêncio" em anotação visível no run — exatamente o que faltou para o incidente aparecer em 2026-08-17. @@ -77,4 +83,6 @@ Nenhuma migration, nenhum Consent/access, nenhum contrato de URL/shape público. - **Mock não sente o provedor.** Unit mocando `generateObject` e e2e com a chave zerada de propósito passaram por 10 dias de código e 5 features quebradas; o único smoke que pegaria era o real — registrado como pendente desde C158/C192 e nunca executado. Um smoke real barato (uma chamada) no primeiro uso em staging teria pegado os 50% de truncamento. - **Cutover de infra pode derrubar capacidade sem mudar uma linha de código.** A chave existia na era Vercel; a migração para o homeserver levou o app e não a chave do runtime — o CI, o lint e os testes não têm como ver isso; só um contrato explícito de envs esperadas no deploy vê. - **Teto de saída é orçamento compartilhado com o raciocínio.** Em modelo com thinking default, `maxOutputTokens` pequeno não limita o tamanho da resposta — trunca o pensamento; a resposta correta é desligar o thinking em tarefas de transformação determinística e/ou dar margem medida, não apertar o teto. +- **Rede é parte do caminho crítico de um LLM externo.** O mesmo request de connect ao provedor teve 1/3 de sucesso em duas amostras (host e container de produção), enquanto a estação passava em 1,1s — degradação por rede é indistinguível de queda do provedor no log da aplicação e cai no mesmo fallback silencioso. +- **Smoke local não valida o alvo.** O caminho do provedor funcionava da estação de trabalho e falhava no homeserver; validar "na minha máquina" não é validar o ambiente que serve a produção. - **Tarefas iguais pedem dono.** A mesma config repetida em cinco call sites garantiu que o incidente atingisse cinco features de uma vez; a defaults compartilhada fecha o vetor de drift até o helper completo chegar.