O Forgejo posta o status do próprio job do safety net como pending durante o
run inteiro: o gate antigo (pending.length === 0) era insatisfazível e o CLI
nunca auto-mergeou nada — todo merge Forgejo até aqui foi manual (merged_by
do dono), inclusive a PR #52 com CI vermelho (narrativa da 'raça' corrigida).
O gate é: rollup CI (PR) / checks presente+verde (cascata inteira assentou),
ignorando o contexto do próprio workflow; mergeable false só após assentar.
- issue-done/plan-issue-ready: env FORGEJO_API_TOKEN (GITHUB_TOKEN 403 em
Issues) + fork guard (checkout de fork roda script do fork com o PAT — RCE)
- forgejo-issue-transition: falha de flip vira exit 1 (job vermelho, nunca
skip silencioso)
- waitForChecks: exige o rollup CI (PR) / checks presente+verde (raça PR #52:
snapshot com jobs não-agendados parecia verde); mergeable=false só conta
como conflito após a cascata assentar; timeouts 30→45min (CLI) e 40→55min
(workflow)
- configure-branch-protection: contexto real CI (PR) / checks* (Forgejo casa
por glob contra 'CI (PR) / checks (pull_request)'; literal 'checks' nunca
casaria), idempotente GET→POST/PATCH, verificação pós-aplicação; decisão
pura em scripts/lib/branch-protection.mjs
- docs honestas (AGENT-OPS, agent-pr-workflow.mdc, CUTOVER-MAIN-ONLY, README)
- testes: raça do rollup/rollup failure/mergeable pré-settle +
branchProtection create/update/noop + glob real
Board "Acompanhe de perto" (S1) ganha o feed automático do canal oficial:
global SocialFeedSettings (admin-only, kill switch + toggle + maxItems +
exclusões por item), loader src/utilities/youtubeFeed.ts (Data API v3
search→statistics, unstable_cache tag social-feed + revalidate 300,
fail-closed com snapshot persistido via SQL direto — revalidateTag dentro
de unstable_cache lança no Next 15.4), card generalizado
(CampaignArticleCard → CampaignContentCard, external com noopener) e merge
por recência com o grande = mais recente elegível. Stub HTTP do YouTube no
webServer do Playwright (porta dev+1000) + 4 e2e novos no describe serial.
Migration 20260818_111748_add_social_feed_settings (não destrutiva).
Closes#18
- scripts/recover-media.mjs: CLI de reconciliação (reconcile/--dry-run/--verify)
— lê rows de media + posts.coverImage, resolve a capa com a mesma lógica do
seed (featured media, senão 1ª imagem inline), PutObject com key = filename;
DB nunca escrito; fail-closed (S3_* completas, ALLOW_REMOTE_DB, echo do alvo,
timeouts curtos, covers só do origin WP e image/*)
- scripts/lib/wpArticles.mjs: fetch ao vivo do WordPress extraído do seed
(sem twin), helpers puros de resolução + row→cover mapping (unit-testados)
- seed-posts.mjs: importa o fetch da lib (comportamento idêntico) e usa
resolveCoverSource em processContent
- runbook de execução no homeserver (impl plan), nota no AGENTS.md, changelog
Closes#10
Medido em probe: com export const revalidate, revalidateTag('posts') para
de regenerar a home (a rota /artigos, sem o export, convergia; a home
ficava stale para sempre). Remove o export e resolve o congelamento da
label relativa por threshold: relativa < 48h, data absoluta além (nunca
mente congelada). Spec: polling positivo do HTML da home pós-revalidate
(ISR serve stale uma vez sob carga) e about:blank antes do cleanup
(prefetch prod de links apagados = 404 no guard).
O warm-up único ainda deixava uma janela de stale-serve sob carga (a
regeneração ISR não completava entre o GET e o goto, e o DOM carregado
nunca refrescava — flake no cleanup sob a suíte paralela). Polling do
estado esperado no HTML do servidor (12x1s) antes do goto torna o
empty-state, o full-state e o cleanup determinísticos.
Sob a suíte completa em paralelo, a regeneração pós-revalidate às vezes
servia a página velha uma vez; o DOM carregado nunca refrescava e o assert
de cleanup falhava (1 flaky no gate). Requisição GET de warm-up entre o
revalidate e o goto torna o estado determinístico.
Bento 1+4 no desktop e carrossel 1/tela no mobile (~390px) lendo o post
collection com isPostVisible fail-closed; seção some inteira sem posts
visíveis. Zero migration/Consent; home vira ISR com tag posts +
revalidate=300 (label relativa não congela).
Merge pull request #27 from fsolla/OPS52-migrar-a-media-de-producao-do-vercel-blob-para-o-garag
OPS52 — Migrar a media de produção do Vercel Blob para o Garage S3 (Closes#3)
- payload.config.ts: vercelBlobStorage -> s3Storage (@payloadcms/storage-s3)
com endpoint Garage (forcePathStyle, region garage); bucket privado — o
staticHandler do plugin streama os objetos em /api/media/file/<filename>,
mantendo o contrato de URL relativa das 40 rows pré-existentes
- src/utilities/mediaStorage.ts: resolveS3StorageEnv fail-closed (S3_* parcial
-> throw no boot com a lista exata; nenhuma -> storage local dev/test)
- seed-posts: remove pre-delete do @vercel/blob (S3 sobrescreve nativamente)
- worktree-env.mjs: s3EnvCopiedLines all-or-nothing (unit-testada)
- envs S3_BUCKET/S3_ENDPOINT/S3_REGION/S3_ACCESS_KEY_ID/S3_SECRET_ACCESS_KEY
no .env.example/.env.test; cloud-setup.sh sem o sed morto do blob
- deps: -storage-vercel-blob -@vercel/blob +storage-s3; importMap regenerado
- docs: impl plan (revisão A->B: Garage v2 sem acesso anônimo na API S3),
AGENTS.md, changelog 2026-08-18-ops52
Job deploy em ci.yml (runs-on host na workstation) streama
scripts/deploy-homeserver.sh para o homeserver via SSH: build dos stages
runner+migrator com secrets do teqo-1313.env, push no registry local,
migrate pelo serviço de maintenance antes do rollout, up + smoke. Zero
secrets novos no Forgejo; HEAD guard + flock; rollback automático com
backup do compose; runbook docs/ops/teqo-1313-deploy.md; invariantes
unit do script (bash -n + estrutura).
O data-time do FullCalendar é uma linha única que atravessa a semana; o
clique force no centro dela resolve o dia pela coordenada x -> coluna do
meio da semana (quinta). Com o término calculado como hoje+1 (C134), o
teste falhava seg/ter quando o término caía antes do início (alert de
validação no overlay). Passa a clicar na coluna do dia atual.
Workflows nativos em .forgejo/workflows/ (ci, ci-pr, agent-pool,
automerge, issue-transition, plan-issue, vercel-promote, archive),
removendo .github/workflows e .github/actions. Runner Forgejo v13 com
services Postgres por porta + DB_HOST via gateway do bridge. Libs de
agente sobre o novo cliente forgejo-api.mjs (zero dependências,
GITHUB_SERVER_URL/FORGEJO_API_URL), helpers de workflow plain-Node.
pnpm issue corrigido (await em função sync) e validado ao vivo. Testes:
forgejoApi.unit.spec.ts novo, pin do matrix do ci.yml atualizado.
Closes#1 (parte 1 — docs/issues/pool seguem na parte 2)
A primeira dobra da home pública agora acompanha as referências do Penpot
em 393, 768, 1024, 1280, 1366 e 1920 px. No desktop, a escala e a posição
horizontal das fotos dependem somente da largura: Solla, Lula e Wagner
ficam ancorados ao rodapé real do hero mesmo quando o conteúdo aumenta a
seção, e um palco centralizado de 1920 px preserva a proximidade entre
retrato e texto em monitores ultrawide. E2E fixa a geometria das seis
referências, transições de proporção, desktops baixos, ultrawide e os
overrides da WCAG 1.4.12; o caso de regressão também roda no Firefox.
O C139 moveu o leader tool de /campanha/contatos para /campanha/meus-contatos
(LEADER_CONTACTS_HOME); campaignConcepts e campaignHomeActions ainda
assertavam o redirect/link antigos — o gate e2e full pegou os dois.
O nome letters-only não carrega o runID, então o discoverOwnedRows do
ownership não encontra o contato criado via formulário (create row/sheet)
e a limpeza da fixture o deixava para trás — cada run vazava 2 contatos
no banco de teste. Deletar explicitamente por nome exato no fim do teste.
Cobre a página /campanha/contatos: recorte por omnibox + WhatsApp na
linha, create row desktop (erro de validação mantém o valor digitado,
conflito de nome com mensagem segura, create válido com POST), gate de
staff (líder cai em meus-contatos) e os sheets mobile (create via FAB
com telefone, edit com conflito + ficha atômica). Nomes criados via UI
são letras-only: o runID hex vira [a-p] (bijection, 128 bits).
O manifest ganha a família própria campaignContacts (antes caía no
campaignHomeActions).
O teste C104 clicava em '17 de agosto de 2026' fixo no picker de término;
quebrava deterministicamente ~2026-09, quando o dia sairia do grid visível.
Agora o alvo é civilDatePlusDays(formatBahiaCivilDate(new Date()), 1) —
sempre dentro do grid (dia do mês ou day 1 do mês seguinte como outside day).
Absorve também o flake pré-existente do redirect canônico ?tab=overview nos
3 sítios de toHaveURL do detalhe (mesmo regex da linha 219, C123).
O import do campaignFixtures.ts (que importa vitest) no fixture e2e crashava a
coleção no processo do Playwright: TypeError: Cannot redefine property:
Symbol($jest-matchers-object) em @vitest/expect — falha nos 2 shards e2e do CI.
purgeMunicipalityResidue/relationId/relationIds/RelationshipValue agora vivem em
tests/helpers/campaignResidue.ts (sem vitest), re-exportados por campaignFixtures
para os importadores existentes.
O cleanup do campaignE2EFixtures estava completo em runs normais (proxy auto-own
cobre contact/stateDeputy); o lixo vinha de runs crashados, cujo cleanup nunca
roda. O claim e2e agora purga resíduo como o int já fazia, e a purga ganhou a
classe dobradinha (stateDeputy vinculados via municipality.stateDeputies + fichas
órfãs com ref-check incluindo stateDeputy — FK ON DELETE RESTRICT).
peopleList.int.spec.ts passa a varrer todas as páginas (lista global de 25/pág)
com dedupe; personDelete/peopleList usam personName() nos nomes fixos, imunes ao
invariante global de nome de dobradinha.
Fase 3 (server) do plano aprovado:
- schemas/contact.ts: contactCreateSchema (nome único via invariante, state
obrigatório, phones opcionais C112); contactFieldUpdateSchema ganha variantes
city e postalCode ('' → clear); mensagens das actions client-safe aqui.
- schemas/personGenders.ts: enum de gênero em módulo folha (ciclo de imports
contact ↔ leadership detectado e quebrado).
- actions/contact.ts: createContact (gate staff + bypass justificado, lock
contact-name:{normalized}) e updateContactField (escopo 'edita o que vê'
via re-check user-threaded para advisor; reorder de telefone primário).
Apagar reusa as actions C100 existentes.
- collections Contact e Supporter: label de gênero 'Outro' → 'Não binário'
(sem migration; nenhum teste pina a label).
- 11 testes unit novos; lint, tsc e prettier limpos.
Fase 2 do plano aprovado. Três módulos em src/utilities/contacts/:
- contactListUrl: contrato de params (q|gender|state|city|ausencia|vinculo|sort|dir|page),
canonicalize, hrefs de sort e where de fonte com 'phones.value' like (ILIKE verificado
no Postgres local).
- contactListData: loader com UM find em contact (user threadado, overrideAccess false),
vínculos (liderancas/dobradinhas/assessores/equipe) como facets sobre o conjunto
legível, filtro/sort/paginação em memória, nulos no fim (precedente B15).
- contactNameInvariant: lock contact-name:{normalized} + prefilter ILIKE + veredito
exato em memória; mensagem de conflito pt-BR.
38 testes unit novos; format + tsc limpos.