14 KiB
Impl: OPS58 — Atualizar artigos de produção com os novos posts do jorgesolla.com.br
Status: aprovado (gate humano 2026-08-18) Atualizado em: 2026-08-18 Issue: #43 Intenção: docs/plans/ops58-sincronizar-posts-prod.md Appetite restante: herdado (~0,5 dia — execução no homeserver + runbook)
Leitura da intenção
- Outcome: todos os artigos do jorgesolla.com.br ainda ausentes em produção
criados como notícia publicada; os existentes intocados (create-only, sem
duplicata); capas dos novos servindo 200 em
/api/media/file/...; cachepostsbustado; procedimento documentado como runbook (padrão OPS52-media). - O que NÃO negociar: create-only (nunca edita/sobrescreve conteúdo
existente); PII intocada; contrato público de URL intocado; guard
ALLOW_REMOTE_DB=trueexplícito (fail-closed mantido); capas dos novos no bucket de prod (envS3_*do stack obrigatório — nunca disco local). - O que reavaliar: a hipótese "ferramenta existe, só executar" precisa de
dois ajustes — (1) o seed não tem modo plan-only (pre-flight de prod
inexistente; o precedente OPS52-media provou o valor de
--dry-runno homeserver); (2) a taxonomy hardcoded está defasada: o WP tem 43 posts e oCLASSIFICATIONcobre 39 — os 4 slugs novos incluem 3 de pré-campanha/campanha que, sem a entrada, caem empoliticasem o tageleitoral, furando o controle de visibilidade eleitoral do repo.
Evidência coletada (2026-08-18, probes read-only)
- E1 — Contagem do WP:
x-wp-total: 43(REST_embed, paginado —fetchArticlesFromWordPresstraz todos).CLASSIFICATIONcobre 39 slugs → 4 slugs novos não mapeados (caem no defaultpoliticahoje):bruno-reis-foi-covarde-detonou-solla-ao-rebater-o-prefeito-sobre-saude-de-salvador(2026-08-17 — resposta política a ataque)chapada-diamantina-recebe-caravana-liderada-pelo-deputado-jorge-solla(2026-07-31 — caravana, pré-campanha)lula-reforca-o-time-de-jeronimo-wagner-rui-e-jorge-solla-na-convencao-estadual-do-pt(2026-08-02 — convenção estadual do PT, campanha)o-pt-tem-que-disputar-o-programa-de-governo-conclamou-jorge-solla-em-plenaria(2026-08-11 — plenária, campanha)
- E2 — Risco do lock
eleitoral: os 3 de campanha acima sem tageleitoralficam visíveis no site público mesmo se a equipe puxar o controlehiddenna janela eleitoral (lock do repo:isPostVisiblefail-closed esconde só posts com tag/categoriaeleitoral). - E3 — Seed sem pre-flight:
seed-posts.mjsnão ecoa o alvo (host do DB) e não tem modo que planeje sem escrever;recover-media.mjs(OPS52) tem ambos — padrão a espelhar (echo do alvo antes de qualquer escrita; modos mutuamente exclusivos; argumento desconhecido morre). - E4 — Homeserver: clone do repo em
~/teqo-deploy(OPS53 — deploy doc; o deploy deixa o clone em detached HEAD no SHA do job). Env do stack em~/stack/teqo-1313.envcomDATABASE_URL=…@postgres:5432— opostgressó resolve dentro da redestack_default; do host, o caminho comprovado é o proxy socatteqo-1313-build-proxyque o deploy script cria idempotentemente (publicado em127.0.0.1:5433no host — é exatamente como o build o usa,deploy-homeserver.sh:91-108). OS3_ENDPOINT=host.docker.internal:3900do stack só resolve dentro do compose; o Garage roda no host (o container alcança viahost-gateway) → do host,S3_ENDPOINT=http://127.0.0.1:3900(a workstation/tailnet usahttp://100.119.220.31:3900). A forma exata de execução do OPS52-media não está registrada de forma consistente entre os docs (o runbook dela mencionacd ~/stack; o deploy doc diz workspace~/teqo-deploy) — o pre-flight do runbook abaixo valida a forma escolhida, e a primeira execução registra o que funcionou na Issue.
Abordagem recomendada
flowchart LR
A[seed-posts.mjs<br/>+ CLASSIFICATION 4 slugs] -->|pnpm db:seed:posts --dry-run| B[pre-flight prod<br/>plano sem escrita]
B -->|ok| C[SEED_MEDIA_CONFIRM=1 ALLOW_REMOTE_DB=true<br/>pnpm db:seed:posts] --> D[posts novos + capas<br/>Garage S3 prod]
D -->|POST /api/revalidate?tag=posts| E[bust cache posts]
E -->|media:recover --verify + curl páginas| F[aceite público]
Opções consideradas (forma de execução):
- A — Estender o dono:
--dry-run(plan-only) noscripts/seed-posts.mjs- classificar os 4 slugs novos no
CLASSIFICATION+ runbook no homeserver. Recomendada.
- classificar os 4 slugs novos no
- B — Executar o seed como está ("às cegas") e confiar no relatório final — o relatório só diz created/skipped depois de já ter escrito em prod; sem echo de alvo, sem chance de revisar "o que será criado" antes.
- C — Script novo de pre-flight separado — twin do fetch/lookup do seed; conhecimento vazaria em 2 módulos.
Opções consideradas (taxonomy dos 4 slugs):
- A2 — Expandir o
CLASSIFICATIONcom os 4 slugs (3 comtags: ['eleitoral'], o bruno-reis empolitica). Recomendada. - B2 — Seed intocado: os 4 caem em
politicasem tageleitoral+ triagem manual pós-sync no admin. - C2 — Bloquear o sync até classificar (abort no unmapped).
Recomendação: A + A2 — o dry-run é o pre-flight que o OPS52 provou no
homeserver (custo baixo: o seed já tem todo o fetch/lookup; dry-run troca
payload.create por contagem), e a classificação dos 4 slugs é aditiva e
create-only (não toca posts existentes — só decide a categoria/tag dos que
serão criados). Os 3 slugs de campanha precisam do tag eleitoral para o
lock de visibilidade do repo continuar valendo — registrá-los como politica
pura seria criar conteúdo de campanha fora do controle eleitoral em plena
janela. A manutenção contínua (próximos slugs) segue fora de escopo, como a
intenção manda.
Rejeitadas: B (sem pre-flight — escreve antes de informar); C (twin do
seed); B2 (fura o lock eleitoral; triagem manual pós-sync é pior que a
classificação no commit, com os 4 slugs já em mãos); C2 (transforma 4 slugs
em gate de conteúdo — a intenção quer o sync desbloqueado).
Componentes / mudanças
scripts/seed-posts.mjs(extensão do dono — comportamento default inalterado):- Parse de args no topo (padrão
recover-media.mjs): só--dry-runaceito; argumento desconhecido → die; echo do alvo (DB host: <host>— sem senha) antes de qualquer trabalho. - Modo dry-run: executa o fetch + lookups de tags/posts existentes +
resolução de covers (
resolveCoverSource, sem download de imagem), e no lugar de cadapayload.createconta/lista "would create" / "would skip" / unmapped / sem-cover. Zero escrita. Relatório final espelhando o SUMMARY atual. CLASSIFICATION: +4 entradas (E1) — 3 comtags: ['eleitoral'].
- Parse de args no topo (padrão
package.json: nada novo —--dry-runé argumento do script existente.- Migration: nenhuma. Access / Consent: nenhum. UI: N/A (Impeccable A — sem mudança de UI; conteúdo/operação).
- Docs: runbook de execução em prod neste impl plan (ordem do homeserver);
entrada
docs/changelog/2026-08-18-ops58.md+pnpm changelog:build; nota curta no AGENTS.md (§ seeding de conteúdo) apontando o runbook.
Fases verificáveis
- Seed (dry-run + taxonomy) — parse de args, echo do alvo, early-returns;
CLASSIFICATION+4. Gates locais:pnpm gate:fast,pnpm format:check,pnpm exec knip,pnpm check:cycles. Validação de comportamento:pnpm db:seed:posts --dry-runcontra o DB local do worktree — relatório com "to create"/"skip"/unmapped e zero posts/tags/media criados (conferir via contagem antes/depois). - Docs + changelog — runbook completo neste impl plan, nota no AGENTS.md,
docs/changelog/2026-08-18-ops58.md,pnpm changelog:build+pnpm changelog:check. - Gates finais + merge —
pnpm gate:fast,format:check,knip,check:cycles,pnpm test(unit+int),pnpm buildlocal; PR → CI verde → merge emmain(deploy automático OPS53). - Execução em prod (pós-merge, runbook — humano): seção abaixo.
Runbook de execução em produção (pós-merge, humano)
- Deploy do merge em
main(CI OPS53) — o seed com--dry-rune a taxonomy chegam ao homeserver com a imagem do SHA. - Pre-flight no homeserver (no clone do repo
~/teqo-deploy— o deploy OPS53 deixa o clone em detached HEAD, então trazer paramainprimeiro; env do stack sourceado;postgressó resolve dentro do compose → usar o proxy socat do build em127.0.0.1:5433; o Garage roda no host →S3_ENDPOINT=http://127.0.0.1:3900):
Esperado: echo do alvo (ssh homeserver cd ~/teqo-deploy git fetch origin main && git checkout main && git pull --ff-only origin main pnpm install # se preciso (hooks — padrão ensure-repo-deps) set -a; source ~/stack/teqo-1313.env; set +a export DATABASE_URL="${DATABASE_URL/@postgres:5432/@127.0.0.1:5433}" # proxy socat do build (postgres só resolve dentro do compose) export S3_ENDPOINT="http://127.0.0.1:3900" # Garage roda no host (host.docker.internal só resolve no compose) ALLOW_REMOTE_DB=true pnpm db:seed:posts --dry-runDB: 127.0.0.1:5433), relatório com os 4 slugs novos a criar (would-create … (cover: url)— URL presente; o download/upload só acontece no sync), slugs existentes a pular, sem escrita. (O dry-run não escreve — segue sem a flagSEED_MEDIA_CONFIRMdo sync, mesmo com asS3_*exportadas.) Se o dry-run falhar com ECONNREFUSED/ENOTFOUND: conferir o proxy (docker ps | grep teqo-1313-build-proxy— o deploy script o cria idempotentemente; se ausente, re-dispatchar o deploy ou rodar o trecho que o cria emscripts/deploy-homeserver.sh) e registrar a forma que funcionou na Issue (primeira execução do runbook). - Sincronizar:
Esperado:SEED_MEDIA_CONFIRM=1 ALLOW_REMOTE_DB=true pnpm db:seed:postsPosts created : 4(os novos),Posts skipped : 39(ou o número real de já existentes), capas baixadas e uploadadas para o Garage de prod (S3_ENDPOINTjá reescrito no passo 2). Nota (OPS60): o sync escreve media no bucket das envsS3_*— o guardSEED_MEDIA_CONFIRM=1é obrigatório (fail-closed; sem ele o seed recusa com exit 1 antes de qualquer escrita). Se o WP ganhar slugs novos entre o pre-flight e aqui, eles entram comopoliticacom warning — registrar na Issue e classificar (admin ou follow-up). - Bust de cache:
Esperado:curl -X POST https://jorgesolla1313.com.br/api/revalidate \ -H "x-revalidate-secret: $REVALIDATE_SECRET"200 { revalidated: true, tag: 'posts' }(o smoke do deploy OPS53 já exercita o endpoint com o secret real — o env está no stack). - Aceite:
- Capas:
ALLOW_REMOTE_DB=true pnpm media:recover --verify— esperado42/44filenames 200: 38 das 40 rows pré-existentes (as 2 exceções conhecidas do OPS52 —fim-escala-6x1.jpg,jorgesolla.jpg— seguem 404 e fazem o verify sair com exit 1; aceite: as únicas falhas são as 2 conhecidas) + as capas novas dos 4 posts (se o dry-run acusoucover: urlpara os 4). - Páginas:
curl -s https://jorgesolla1313.com.br/ | grep <slug-novo>ecurl -o /dev/null -w "%{http_code}" https://jorgesolla1313.com.br/noticia/politica/<slug-novo>→ 200 para os 4 novos; conferir também/artigose a seção S1 da home de campanha.
- Capas:
- Registro: resumo na Issue #43 (created/skipped, exceções de capa, slugs unmapped adicionais, forma exata de execução usada no passo 2).
Rabbit holes / Não escopo (engenharia)
--verifyde posts em código — curl no runbook basta; não criar ferramenta para o aceite.- Upsert de edits do WP / sync agendado / hidden flag — fora de escopo da intenção; itens separados quando doer.
- Testar o sync ponta a ponta fora do homeserver — impossível por
contrato (sem DB de prod, sem
S3_*); a prova E2E é o runbook (padrão OPS52 fase 3). - Unidade de teste para o parse de args do seed — mesmo padrão do
recover-media.mjs(parse inline, sem teste dedicado); o write path não muda (create-only) e o dry-run é validado contra o DB local.
Riscos e mitigação
- R1 — Host do homeserver não alcança
postgres(DB só resolve dentro destack_default; a forma comprovada do host é o proxy socat do build em127.0.0.1:5433). Mitigação: o pre-flight (dry-run) valida em segundos; se o proxy estiver ausente, o deploy script o recria (re-dispatch); a forma real usada na primeira execução é registrada na Issue. - R2 — Capa nova não resolve (WP mudou/removeu a imagem). Mitigação:
post criado sem cover → card degrada para a banda cinza existente (sem
placeholder); exceção registrada na Issue;
media:recover --verifyacusa (as únicas falhas aceitáveis são as 2 exceções conhecidas do OPS52). - R3 — Conteúdo eleitoral sem tag
eleitoral(se o gate rejeitar A2). Mitigação: triagem manual no admin imediatamente pós-sync (passo 3 do runbook vira passo de classificação obrigatório). - R4 —
REVALIDATE_SECRETausente no env do stack. Mitigação: o smoke do deploy (OPS53) já testa/api/revalidatecom o secret real — presença garantida; conferir no pre-flight se o curl der 401/500. - R5 — WP ganha slugs novos entre o commit e a execução (fetch é live). Mitigação: relatório final lista unmapped; runbook manda registrar e classificar (admin ou follow-up) — nunca bloqueia o sync.
Aceite de engenharia
- Aceite de produto da intenção ainda coberto: posts ausentes criados, existentes intocados (create-only), capas 200, cache bustado, runbook documentado (padrão OPS52-media)
- Invariantes AGENTS/engineering-standards:
ALLOW_REMOTE_DB=trueexplícito mantido; identificadores em inglês; zero access/Consent/ transação/migration tocados; edit the owner (seed estendido, sem twin) - Testes/validação de domínio:
--dry-runcontra DB local (relatório sem escrita); gates da checklist do AGENTS; prova E2E é o runbook do homeserver - Docs: runbook de execução em prod + nota AGENTS.md + changelog