From 8b3f102ea2f330f7e374db7389ddbf02e18ae9fa Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Fri, 25 Sep 2026 10:43:00 -0300 Subject: [PATCH] docs(C220-FOLLOWUP): registra o efeito colateral do ufw (Garage) e a correcao no post-mortem/runbook --- docs/ops/teqo-1313-deploy.md | 46 +++++++++---------- .../2026-09-25-instagram-cdn-ipv4-instavel.md | 31 +++++++------ 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/docs/ops/teqo-1313-deploy.md b/docs/ops/teqo-1313-deploy.md index 189aa89d..4fa4b79d 100644 --- a/docs/ops/teqo-1313-deploy.md +++ b/docs/ops/teqo-1313-deploy.md @@ -305,29 +305,29 @@ a correção se re-mergeia e o próximo deploy completo publica. ## Falhas conhecidas -| Sintoma | Causa | Tratamento | -| -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Prod regride para um SHA antigo após re-dispatch fora de ordem | O `flock` serializa mas não ordena; o "already deployed" não pega revision diferente | Evitar re-dispatchar SHA antigo (o requeue da OPS104 sempre despacha o head atual); se acontecer, re-dispatchar o `main` atual (rollback manual disponível na seção Rollback). Revisitar se uma regressão fora-de-ordem acontecer mesmo com o requeue | -| Job verde sem deploy ("already deployed") | O container rodando já tem a revision do SHA do job (ex.: run duplicado da mesma HEAD — `workflow_dispatch`, ou push + requeue da OPS104) | Nada a fazer — o site já roda esse SHA. Forçar rebuild da MESMA HEAD não é suportado via dispatch: edite o tag da imagem no compose (`image: localhost:5000/teqo-1313:-rebuild`) e `docker compose up -d`, ou espere um commit novo | -| Merge durante aprovação pendente de produção: staging do run novo ficava `pending` (resolvido) | O job `waiting` de approval **segurava o grupo** `deploy-homeserver` (a avaliação de concurrency acontece antes do approval — comportamento do GitHub) | **Resolvido na OPS107:** o `concurrency` vive só no `deploy-staging` (que nunca espera approval); o `deploy-production` não ocupa lane, então runs novos fazem staging e ficam aprováveis. Aprove só o run escolhido e rejeite os demais — runs não aprovados não publicam; multi-approve = o último deploy executado vence (rollback manual se um SHA indesejado publicar) | -| Merge durante `deploy-production` `in_progress` não gera requeue | O push nasce, o `preflight` vê o deploy ativo e pula; o requeue daquele run já rodou (logo após o staging) | Nada nasce até o próximo merge ou `workflow_dispatch`. Revisitar na primeira ocorrência real → 2º requeue após produção verde, ou refinar o guard | -| Job de deploy nunca roda / runner offline | Runner self-hosted do homeserver não instalado ou parado (passo manual do cutover OPS71) | Instalar/religar o runner (labels `self-hosted`, `homeserver`); os PRs e o `verify` não dependem dele — só os deploys | -| Deploy dispara mas o job `verify` falha | Regressão real na suíte full | Corrigir e re-dispatchar — o homeserver nunca é tocado | -| `No such image` ao recriar o container fora de deploy (ex.: `compose up -d` pós-reboot/migração) | O compose referenciava uma tag local bare (`teqo-1313:`) que desapareceu sem recriação (achado INF8/INF13, 24/08: só restava a ref do registry) | Corrigido no INF13: o compose passa a referenciar a tag **qualificada** (`localhost:5000/teqo-1313:`) a partir do primeiro deploy pós-fix — até lá, produção ainda roda o compose bare (recrie o alias via `docker tag` se necessário). Recovery geral: `docker pull localhost:5000/teqo-1313:` (+ `docker tag` se o compose em uso tiver tag bare) e `docker compose up -d` | -| Deploy para logo após o migrate ("Done." e nada mais, EXIT=0) | `docker compose run` anexa stdin por padrão — o container consome o resto do script; o bash chega a EOF e termina sem rodar o rollout | O script já usa `< /dev/null` no `run --rm` do migrate (não remover); sintoma visto 2026-08-17 no primeiro deploy | -| Build falha: `network mode "stack_default" not supported by buildkit` | BuildKit (drivers docker e docker-container) recusa rede bridge custom no `--network` | O script builda com `--network host` + proxy socat do ambiente (prod `teqo-1313-build-proxy` em 127.0.0.1:5433; staging `teqo-staging-build-proxy` em 127.0.0.1:5434, na `stack_default`, criado idempotentemente com `restart: unless-stopped`) + `DATABASE_URL` reescrita para o loopback | -| Build OOM no homeserver | Laptop 8c/16GB com o stack ativo (~12GB livres medidos) | Re-dispatch; se recorrente, item futuro: build na workstation com túnel | -| Build do runner falha: `relation "..." does not exist` no `next build` | Migration nova criou tabela lida em geração estática. Pré-OPS66 a ordem era build→migrate e o deploy morria aqui para sempre (incidente 2026-08-18, S2) | Pós-OPS66 não deve ocorrer: migrate roda antes do build do runner. Se reaparecer, confira no log se o passo migrate rodou; recovery manual: `docker build --target migrator` + `docker run --rm --network stack_default --env-file ~/stack/teqo-1313.env localhost:5000/teqo-1313-migrator:` e re-dispatch do workflow | -| Workspace clone sem o SHA novo | O workspace antigo apontava para o Forgejo local (pré-OPS71) e o fetch não achou o SHA | O script agora re-aponta `origin` para `TEQO_REPO_URL` (idempotente) antes do fetch — se ainda falhar, `git -C ~/teqo-deploy remote -v` e corrigir manualmente | -| Migrate falha | Drift/erro de schema | Job vermelho; site segue no container antigo; corrigir e re-mergear | -| Smoke falha pós-up | Regressão de runtime | Rollback automático (restore + `up -d`) + job vermelho; investigar | -| `deploy-staging` falha cedo (`teqo-staging.env`/serviço ausente) | Bootstrap do staging incompleto (DB, env file, serviços no compose ou environment `staging`) | Rodar o bootstrap da seção Staging; produção nunca é tocada (staging vermelho fail-closa o job `deploy-production`) | -| `verify` vermelho | Regressão real na suíte full (ou flake — #882/#906) | O `auto-unblock.yml` dispara o agente sozinho (OPS106): acompanhe a Issue `auto-unblock` (token) e o PR `fix/*`. Sem PR, o token fica `blocked` — inspecione o log em `~/teqo-unblock/logs/`. Correção manual segue possível (re-merge ou `workflow_dispatch`) | -| Agente de desbloqueio não disparou (token inexistente) | Bootstrap do homeserver incompleto — opencode sem auth, Node/pnpm fora do PATH do runner, PAT ausente/sem push, Docker/porta 5432, label ausente | Rodar `pnpm unblock:check` no homeserver (com `GITHUB_TOKEN` e `AUTOMERGE_PAT` exportados) e seguir a seção Auto-unblock (OPS106) abaixo | -| Launcher `auto-unblock` morre em `fetch failed` (nenhum token criado) | Node fetch ignora HTTP(S)\_PROXY sem NODE_USE_ENV_PROXY (Node ≥24); no homeserver o GitHub só sai pelo proxy CONNECT do runner | OPS126: `scripts/auto-unblock.sh` liga o `NODE_USE_ENV_PROXY` quando há proxy no env; manter `HTTPS_PROXY/HTTP_PROXY` no `~/actions-runner/.env`. Re-dispatch ou aguarde a próxima falha do `verify` | -| Agente morre com exit 127 (`opencode`: No such file or directory) | Serviço do runner não carrega o profile → `~/.opencode/bin` fora do PATH; ou opencode nem instalado | OPS126: `resolveOpenCodeBinary` (`OPENCODE_BIN` → `$HOME/.opencode/bin` → PATH); o launcher falha fechado (token `blocked`) se faltar. Instale/autentique via `pnpm unblock:check` | -| Token `auto-unblock` preso (agente morto, sem PR) | Wrapper morreu (OOM/timeout/erro) — lock livre, token < TTL se torna `skip_comment` na próxima falha | O TTL de 3h reclaima o token automaticamente (comenta + fecha + novo agente). Antes disso, inspecione o log e a branch `fix/*`; para liberar na hora, feche a Issue token com um comentário | -| Capa do Instagram na home em `500` / link do próprio Instagram na Central sempre "Instagram indisponível no momento" | O container é IPv4-only e a rota IPv4 até o CDN do Instagram (`instagram.fssa2-1.fna.fbcdn.net`) é instável nesta rede; a API (`graph.instagram.com`) funciona, mas o arquivo não chega (`media_url` presente e download estourando) | A rede `teqo-ipv6` (`enable_ipv6`, ULA `fd00:1313:1313::/64`) está anexada a `teqo-1313`/`teqo-staging` no compose (backup `docker-compose.yml.pre-ipv6-*`). Se voltar: confirme IPv6 no container (`/proc/net/if_inet6`) e o fetch do CDN de dentro dele; o resolver agora faz retry com teto de conexão. Post-mortem: `docs/postmortems/2026-09-25-instagram-cdn-ipv4-instavel.md` | +| Sintoma | Causa | Tratamento | +| -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Prod regride para um SHA antigo após re-dispatch fora de ordem | O `flock` serializa mas não ordena; o "already deployed" não pega revision diferente | Evitar re-dispatchar SHA antigo (o requeue da OPS104 sempre despacha o head atual); se acontecer, re-dispatchar o `main` atual (rollback manual disponível na seção Rollback). Revisitar se uma regressão fora-de-ordem acontecer mesmo com o requeue | +| Job verde sem deploy ("already deployed") | O container rodando já tem a revision do SHA do job (ex.: run duplicado da mesma HEAD — `workflow_dispatch`, ou push + requeue da OPS104) | Nada a fazer — o site já roda esse SHA. Forçar rebuild da MESMA HEAD não é suportado via dispatch: edite o tag da imagem no compose (`image: localhost:5000/teqo-1313:-rebuild`) e `docker compose up -d`, ou espere um commit novo | +| Merge durante aprovação pendente de produção: staging do run novo ficava `pending` (resolvido) | O job `waiting` de approval **segurava o grupo** `deploy-homeserver` (a avaliação de concurrency acontece antes do approval — comportamento do GitHub) | **Resolvido na OPS107:** o `concurrency` vive só no `deploy-staging` (que nunca espera approval); o `deploy-production` não ocupa lane, então runs novos fazem staging e ficam aprováveis. Aprove só o run escolhido e rejeite os demais — runs não aprovados não publicam; multi-approve = o último deploy executado vence (rollback manual se um SHA indesejado publicar) | +| Merge durante `deploy-production` `in_progress` não gera requeue | O push nasce, o `preflight` vê o deploy ativo e pula; o requeue daquele run já rodou (logo após o staging) | Nada nasce até o próximo merge ou `workflow_dispatch`. Revisitar na primeira ocorrência real → 2º requeue após produção verde, ou refinar o guard | +| Job de deploy nunca roda / runner offline | Runner self-hosted do homeserver não instalado ou parado (passo manual do cutover OPS71) | Instalar/religar o runner (labels `self-hosted`, `homeserver`); os PRs e o `verify` não dependem dele — só os deploys | +| Deploy dispara mas o job `verify` falha | Regressão real na suíte full | Corrigir e re-dispatchar — o homeserver nunca é tocado | +| `No such image` ao recriar o container fora de deploy (ex.: `compose up -d` pós-reboot/migração) | O compose referenciava uma tag local bare (`teqo-1313:`) que desapareceu sem recriação (achado INF8/INF13, 24/08: só restava a ref do registry) | Corrigido no INF13: o compose passa a referenciar a tag **qualificada** (`localhost:5000/teqo-1313:`) a partir do primeiro deploy pós-fix — até lá, produção ainda roda o compose bare (recrie o alias via `docker tag` se necessário). Recovery geral: `docker pull localhost:5000/teqo-1313:` (+ `docker tag` se o compose em uso tiver tag bare) e `docker compose up -d` | +| Deploy para logo após o migrate ("Done." e nada mais, EXIT=0) | `docker compose run` anexa stdin por padrão — o container consome o resto do script; o bash chega a EOF e termina sem rodar o rollout | O script já usa `< /dev/null` no `run --rm` do migrate (não remover); sintoma visto 2026-08-17 no primeiro deploy | +| Build falha: `network mode "stack_default" not supported by buildkit` | BuildKit (drivers docker e docker-container) recusa rede bridge custom no `--network` | O script builda com `--network host` + proxy socat do ambiente (prod `teqo-1313-build-proxy` em 127.0.0.1:5433; staging `teqo-staging-build-proxy` em 127.0.0.1:5434, na `stack_default`, criado idempotentemente com `restart: unless-stopped`) + `DATABASE_URL` reescrita para o loopback | +| Build OOM no homeserver | Laptop 8c/16GB com o stack ativo (~12GB livres medidos) | Re-dispatch; se recorrente, item futuro: build na workstation com túnel | +| Build do runner falha: `relation "..." does not exist` no `next build` | Migration nova criou tabela lida em geração estática. Pré-OPS66 a ordem era build→migrate e o deploy morria aqui para sempre (incidente 2026-08-18, S2) | Pós-OPS66 não deve ocorrer: migrate roda antes do build do runner. Se reaparecer, confira no log se o passo migrate rodou; recovery manual: `docker build --target migrator` + `docker run --rm --network stack_default --env-file ~/stack/teqo-1313.env localhost:5000/teqo-1313-migrator:` e re-dispatch do workflow | +| Workspace clone sem o SHA novo | O workspace antigo apontava para o Forgejo local (pré-OPS71) e o fetch não achou o SHA | O script agora re-aponta `origin` para `TEQO_REPO_URL` (idempotente) antes do fetch — se ainda falhar, `git -C ~/teqo-deploy remote -v` e corrigir manualmente | +| Migrate falha | Drift/erro de schema | Job vermelho; site segue no container antigo; corrigir e re-mergear | +| Smoke falha pós-up | Regressão de runtime | Rollback automático (restore + `up -d`) + job vermelho; investigar | +| `deploy-staging` falha cedo (`teqo-staging.env`/serviço ausente) | Bootstrap do staging incompleto (DB, env file, serviços no compose ou environment `staging`) | Rodar o bootstrap da seção Staging; produção nunca é tocada (staging vermelho fail-closa o job `deploy-production`) | +| `verify` vermelho | Regressão real na suíte full (ou flake — #882/#906) | O `auto-unblock.yml` dispara o agente sozinho (OPS106): acompanhe a Issue `auto-unblock` (token) e o PR `fix/*`. Sem PR, o token fica `blocked` — inspecione o log em `~/teqo-unblock/logs/`. Correção manual segue possível (re-merge ou `workflow_dispatch`) | +| Agente de desbloqueio não disparou (token inexistente) | Bootstrap do homeserver incompleto — opencode sem auth, Node/pnpm fora do PATH do runner, PAT ausente/sem push, Docker/porta 5432, label ausente | Rodar `pnpm unblock:check` no homeserver (com `GITHUB_TOKEN` e `AUTOMERGE_PAT` exportados) e seguir a seção Auto-unblock (OPS106) abaixo | +| Launcher `auto-unblock` morre em `fetch failed` (nenhum token criado) | Node fetch ignora HTTP(S)\_PROXY sem NODE_USE_ENV_PROXY (Node ≥24); no homeserver o GitHub só sai pelo proxy CONNECT do runner | OPS126: `scripts/auto-unblock.sh` liga o `NODE_USE_ENV_PROXY` quando há proxy no env; manter `HTTPS_PROXY/HTTP_PROXY` no `~/actions-runner/.env`. Re-dispatch ou aguarde a próxima falha do `verify` | +| Agente morre com exit 127 (`opencode`: No such file or directory) | Serviço do runner não carrega o profile → `~/.opencode/bin` fora do PATH; ou opencode nem instalado | OPS126: `resolveOpenCodeBinary` (`OPENCODE_BIN` → `$HOME/.opencode/bin` → PATH); o launcher falha fechado (token `blocked`) se faltar. Instale/autentique via `pnpm unblock:check` | +| Token `auto-unblock` preso (agente morto, sem PR) | Wrapper morreu (OOM/timeout/erro) — lock livre, token < TTL se torna `skip_comment` na próxima falha | O TTL de 3h reclaima o token automaticamente (comenta + fecha + novo agente). Antes disso, inspecione o log e a branch `fix/*`; para liberar na hora, feche a Issue token com um comentário | +| Capa do Instagram na home em `500` / link do próprio Instagram na Central sempre "Instagram indisponível no momento" | O container é IPv4-only e a rota IPv4 até o CDN do Instagram (`instagram.fssa2-1.fna.fbcdn.net`) é instável nesta rede; a API (`graph.instagram.com`) funciona, mas o arquivo não chega (`media_url` presente e download estourando) | A rede `teqo-ipv6` (`enable_ipv6`; sub-redes **fixadas** `10.0.13.0/24` + ULA `fd00:1313:1313::/64`) está anexada a `teqo-1313`/`teqo-staging` no compose (backup `docker-compose.yml.pre-ipv6-*`). Com duas redes, o `host-gateway`/default route saem pela sub-rede nova: o Garage (3900) precisa do par de regras de ufw IN + FWD para `10.0.13.0/24` (espelha o allow do `10.0.11.0/24`) — sem elas o upload da peça falha com `connect ETIMEDOUT 10.0.0.1:3900`. Se voltar: confirme IPv6 no container (`/proc/net/if_inet6`), o fetch do CDN e o TCP/HTTP do Garage de dentro dele; o resolver agora faz retry com teto de conexão. Post-mortem: `docs/postmortems/2026-09-25-instagram-cdn-ipv4-instavel.md` | ## Auto-unblock (OPS106) diff --git a/docs/postmortems/2026-09-25-instagram-cdn-ipv4-instavel.md b/docs/postmortems/2026-09-25-instagram-cdn-ipv4-instavel.md index 200cd98f..b561c396 100644 --- a/docs/postmortems/2026-09-25-instagram-cdn-ipv4-instavel.md +++ b/docs/postmortems/2026-09-25-instagram-cdn-ipv4-instavel.md @@ -15,16 +15,18 @@ ## Timeline -| Momento | Data/hora | Evento | -| ----------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Início provável | não apurado | A rota IPv4 da rede até o CDN do Instagram degradar; o container (IPv4-only) depende dela desde sempre | -| C220-FOLLOWUP em prod | 2026-09-25 ~03:34 UTC | Deploy do SHA `3cdd1cd8` (inclui o early-stop do PR #1332); o sintoma persistiu | -| Detecção | 2026-09-24 ~22:09 BRT | Relato do humano; a causa residual só foi isolada com o diagnóstico read-only de 2026-09-25 | -| Diagnóstico (read-only) | 2026-09-25 ~08:30–09:30 BRT | API devolve `media_url` presente; otimizador de imagem de prod → 500; host e container com ~3/8 de sucesso no IPv4 e 100% no IPv6; container sem rota IPv6 | -| Correção de infra | 2026-09-25 ~09:35 BRT | Rede `teqo-ipv6` (enable_ipv6, ULA) anexada a `teqo-1313`/`teqo-staging` no compose do homeserver + recreate; verificação 10/10 e otimizador 200 | -| Correção de código (PR) | pendente | Retry com teto de conexão no download do resolver (`contentPieceLink.ts`) — a preencher número | -| Deploy (código) | pendente | Merge em `main` dispara deploy; produção só com approve humano no environment `production` | -| Verificado em prod | pendente | Aguardando o reteste do humano na Central (a rede já está corrigida em prod) | +| Momento | Data/hora | Evento | +| ----------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Início provável | não apurado | A rota IPv4 da rede até o CDN do Instagram degradar; o container (IPv4-only) depende dela desde sempre | +| C220-FOLLOWUP em prod | 2026-09-25 ~03:34 UTC | Deploy do SHA `3cdd1cd8` (inclui o early-stop do PR #1332); o sintoma persistiu | +| Detecção | 2026-09-24 ~22:09 BRT | Relato do humano; a causa residual só foi isolada com o diagnóstico read-only de 2026-09-25 | +| Diagnóstico (read-only) | 2026-09-25 ~08:30–09:30 BRT | API devolve `media_url` presente; otimizador de imagem de prod → 500; host e container com ~3/8 de sucesso no IPv4 e 100% no IPv6; container sem rota IPv6 | +| Correção de infra | 2026-09-25 ~09:35 BRT | Rede `teqo-ipv6` (enable_ipv6, ULA) anexada a `teqo-1313`/`teqo-staging` no compose do homeserver + recreate; verificação 10/10 e otimizador 200 | +| Efeito colateral do S3 | 2026-09-25 ~09:50 BRT | A rede nova assumiu o `host-gateway` (`10.0.0.1`) e o default route do container (fonte `10.0.13.x`), fora do allow do ufw do Garage (`10.0.11.0/24`): upload da peça falhou com `connect ETIMEDOUT 10.0.0.1:3900` | +| Correção do S3 | 2026-09-25 ~10:35 BRT | Sub-redes fixadas no compose (`10.0.13.0/24` + `fd00:1313:1313::/64`) e ufw `allow`/`route allow` de `10.0.13.0/24` → 3900 (INPUT + FWD), espelhando o padrão do `10.0.11.0/24`; Garage, ffmpeg e serving de mídia reverificados | +| Correção de código (PR) | 2026-09-25 ~10:00 BRT | PR #1349 mergeado (retry com teto de conexão no download do resolver) | +| Deploy (código) | pendente | Merge em `main` dispara deploy; produção só com approve humano no environment `production` | +| Verificado em prod | pendente | Aguardando o reteste do humano na Central (a rede já está corrigida em prod) | ## O bug @@ -52,15 +54,15 @@ O container de produção é **IPv4-only** (Docker sem `ipv6`; `/proc/net/if_ine ## Correção -- **Infra (fora do repo):** rede `teqo-ipv6` (`enable_ipv6: true`, subnet ULA `fd00:1313:1313::/64`) no `~/stack/docker-compose.yml` do homeserver, anexada a `teqo-1313` e `teqo-staging` (mantendo a `default`). Backup do compose em `docker-compose.yml.pre-ipv6-`; containers recriados (`docker compose up -d teqo-1313 teqo-staging`). Rollback: restaurar o backup e `docker compose up -d teqo-1313 teqo-staging`. -- **Código (este PR):** `downloadContentPieceMedia` em `src/utilities/content/contentPieceLink.ts` — até 6 tentativas com teto de 15 s para a fase de conexão/cabeçalhos (o corpo de uma resposta conectada mantém o orçamento de 3 min). Um `fetch` perdido deixa de perder a peça; tentativas esgotadas continuam virando `indisponivel`, e resposta não-2xx não é repetida. +- **Infra (fora do repo):** rede `teqo-ipv6` (`enable_ipv6: true`, sub-redes fixadas `10.0.13.0/24` + ULA `fd00:1313:1313::/64`) no `~/stack/docker-compose.yml` do homeserver, anexada a `teqo-1313` e `teqo-staging` (mantendo a `default`). Backup do compose em `docker-compose.yml.pre-ipv6-`; containers recriados (`docker compose up -d teqo-1313 teqo-staging`). **Efeito colateral corrigido:** com duas redes, o `host-gateway` (`host.docker.internal` → `10.0.0.1`) e o default route passaram a sair pela sub-rede nova (`10.0.13.x`), que o ufw não liberava para o Garage — o upload da peça estourava em `10.0.0.1:3900`; a correção fixa as sub-redes no compose e libera `10.0.13.0/24` no 3900 (INPUT + FWD). Rollback: restaurar o backup, reverter as regras de ufw novas e `docker compose up -d teqo-1313 teqo-staging`. +- **Código (PR #1349):** `downloadContentPieceMedia` em `src/utilities/content/contentPieceLink.ts` — até 6 tentativas com teto de 15 s para a fase de conexão/cabeçalhos (o corpo de uma resposta conectada mantém o orçamento de 3 min). Um `fetch` perdido deixa de perder a peça; tentativas esgotadas continuam virando `indisponivel`, e resposta não-2xx não é repetida. ## Verificação - Teste de regressão: `tests/int/contentPiece.int.spec.ts` — "retries a stranded media download and still extracts the piece" — falha sem o fix (1 tentativa → `indisponivel`) e passa com (4 tentativas → mídia criada) - Suíte: int contentPiece 39/39; `pnpm gate:fast` verde (lint/typecheck/unit full 4669) -- Infra em prod: 10/10 downloads do CDN dentro do container `teqo-1313`; `/_next/image` de capa IG → `200` (3/3) -- CI: pendente no PR; Prod: pendente do reteste do humano na Central +- Infra em prod: 10/10 downloads do CDN dentro do container `teqo-1313`; `/_next/image` de capa IG → `200` (3/3); Garage TCP/HTTP OK do container e serving de mídia (`/api/media/file/...`) → `200` (7,3 MB); vídeo real do reel com H.264+AAC e ffmpeg do container extraindo chunk MP3 (520 KB); chaves `DEEPINFRA_API_KEY`/`DEEPSEEK_API_KEY` presentes +- CI do PR #1349: verde; Prod: pendente do reteste do humano na Central ## Prevenção @@ -83,3 +85,4 @@ O container de produção é **IPv4-only** (Docker sem `ipv6`; `/proc/net/if_ine - **Diagnóstico de rede precisa dos dois lados:** medir host × container e IPv4 × IPv6 com repetição revelou o padrão que uma única tentativa não mostra. - **"A API entrega o arquivo" não significa "o arquivo chega":** `media_url` presente, `copyright` ausente e o download falhando por rota. - **O CDN muda de host/edge sem aviso:** o post-mortem de 2026-09-11 já tinha visto `cdninstagram` → `fbcdn`; desta vez o edge IPv4 novo é que não é alcançável de forma confiável. +- **Rede adicional muda o caminho de saída do container:** com duas redes, o `host-gateway` e o default route migraram para a rede nova — a regra de firewall por sub-rede do Garage (3900) deixou de casar e o upload passou a estourar. Fixar as sub-redes no compose e espelhar o allow no ufw mantém o caminho estável; qualquer rede nova no stack precisa revisar as regras por sub-rede.