mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-14 08:53:11 +00:00
246 lines
14 KiB
Markdown
246 lines
14 KiB
Markdown
# 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/...`; cache
|
|
`posts` bustado; 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=true` explícito (fail-closed mantido); capas dos novos no
|
|
bucket de prod (env `S3_*` 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-run` no
|
|
homeserver); (2) a taxonomy hardcoded está **defasada**: o WP tem 43 posts e
|
|
o `CLASSIFICATION` cobre 39 — os 4 slugs novos incluem 3 de
|
|
pré-campanha/campanha que, sem a entrada, caem em `politica` **sem o tag
|
|
`eleitoral`**, 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 —
|
|
`fetchArticlesFromWordPress` traz todos). `CLASSIFICATION` cobre 39 slugs →
|
|
**4 slugs novos não mapeados** (caem no default `politica` hoje):
|
|
- `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 tag
|
|
`eleitoral` ficam visíveis no site público mesmo se a equipe puxar o
|
|
controle `hidden` na janela eleitoral (lock do repo:
|
|
`isPostVisible` fail-closed esconde só posts com tag/categoria `eleitoral`).
|
|
- **E3 — Seed sem pre-flight:** `seed-posts.mjs` nã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.env` com `DATABASE_URL=…@postgres:5432` — o `postgres`
|
|
só resolve **dentro** da rede `stack_default`; do host, o caminho comprovado
|
|
é o **proxy socat `teqo-1313-build-proxy`** que o deploy script cria
|
|
idempotentemente (publicado em `127.0.0.1:5433` no host — é exatamente como
|
|
o build o usa, `deploy-homeserver.sh:91-108`). O `S3_ENDPOINT=host.docker.internal:3900`
|
|
do stack só resolve dentro do compose; o Garage roda **no host** (o container
|
|
alcança via `host-gateway`) → do host, `S3_ENDPOINT=http://127.0.0.1:3900`
|
|
(a workstation/tailnet usa `http://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 menciona `cd ~/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
|
|
|
|
```mermaid
|
|
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) no `scripts/seed-posts.mjs`
|
|
- classificar os 4 slugs novos no `CLASSIFICATION` + runbook no homeserver.
|
|
**Recomendada.**
|
|
- 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 `CLASSIFICATION` com os 4 slugs** (3 com
|
|
`tags: ['eleitoral']`, o bruno-reis em `politica`). **Recomendada.**
|
|
- B2 — Seed intocado: os 4 caem em `politica` sem tag `eleitoral` + 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-run`
|
|
aceito; 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 cada `payload.create` conta/lista "would create" /
|
|
"would skip" / unmapped / sem-cover. Zero escrita. Relatório final
|
|
espelhando o SUMMARY atual.
|
|
- `CLASSIFICATION`: +4 entradas (E1) — 3 com `tags: ['eleitoral']`.
|
|
- **`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
|
|
|
|
1. **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-run` contra o DB local do worktree — relatório
|
|
com "to create"/"skip"/unmapped e **zero** posts/tags/media criados
|
|
(conferir via contagem antes/depois).
|
|
2. **Docs + changelog** — runbook completo neste impl plan, nota no AGENTS.md,
|
|
`docs/changelog/2026-08-18-ops58.md`, `pnpm changelog:build` +
|
|
`pnpm changelog:check`.
|
|
3. **Gates finais + merge** — `pnpm gate:fast`, `format:check`, `knip`,
|
|
`check:cycles`, `pnpm test` (unit+int), `pnpm build` local; PR → CI verde
|
|
→ merge em `main` (deploy automático OPS53).
|
|
4. **Execução em prod (pós-merge, runbook — humano):** seção abaixo.
|
|
|
|
## Runbook de execução em produção (pós-merge, humano)
|
|
|
|
1. **Deploy do merge em `main`** (CI OPS53) — o seed com `--dry-run` e a
|
|
taxonomy chegam ao homeserver com a imagem do SHA.
|
|
2. **Pre-flight no homeserver** (no clone do repo `~/teqo-deploy` — o deploy
|
|
OPS53 deixa o clone em **detached HEAD**, então trazer para `main` primeiro;
|
|
env do stack sourceado; `postgres` só resolve dentro do compose → usar o
|
|
**proxy socat do build** em `127.0.0.1:5433`; o Garage roda no host →
|
|
`S3_ENDPOINT=http://127.0.0.1:3900`):
|
|
```bash
|
|
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-run
|
|
```
|
|
Esperado: echo do alvo (`DB: 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 flag `SEED_MEDIA_CONFIRM` do sync, mesmo com as `S3_*`
|
|
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 em
|
|
`scripts/deploy-homeserver.sh`) e registrar a forma que funcionou na Issue
|
|
(primeira execução do runbook).
|
|
3. **Sincronizar:**
|
|
```bash
|
|
SEED_MEDIA_CONFIRM=1 ALLOW_REMOTE_DB=true pnpm db:seed:posts
|
|
```
|
|
Esperado: `Posts 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_ENDPOINT` já reescrito no passo 2). **Nota (OPS60):** o sync escreve
|
|
media no bucket das envs `S3_*` — o guard `SEED_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 como `politica` com warning — registrar na
|
|
Issue e classificar (admin ou follow-up).
|
|
4. **Bust de cache:**
|
|
```bash
|
|
curl -X POST https://jorgesolla1313.com.br/api/revalidate \
|
|
-H "x-revalidate-secret: $REVALIDATE_SECRET"
|
|
```
|
|
Esperado: `200 { revalidated: true, tag: 'posts' }` (o smoke do deploy
|
|
OPS53 já exercita o endpoint com o secret real — o env está no stack).
|
|
5. **Aceite:**
|
|
- Capas: `ALLOW_REMOTE_DB=true pnpm media:recover --verify` — esperado
|
|
`42/44` filenames 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 acusou
|
|
`cover: url` para os 4).
|
|
- Páginas: `curl -s https://jorgesolla1313.com.br/ | grep <slug-novo>`
|
|
e `curl -o /dev/null -w "%{http_code}" https://jorgesolla1313.com.br/noticia/politica/<slug-novo>`
|
|
→ 200 para os 4 novos; conferir também `/artigos` e a seção S1 da home
|
|
de campanha.
|
|
6. **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)
|
|
|
|
- **`--verify` de 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 de
|
|
`stack_default`; a forma comprovada do host é o proxy socat do build em
|
|
`127.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 --verify` acusa
|
|
(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_SECRET` ausente no env do stack.** Mitigação: o smoke
|
|
do deploy (OPS53) já testa `/api/revalidate` com 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=true`
|
|
explí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-run` contra 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
|