Fix manual dispatch em 18 formulários de /campanha que usavam <form action={submitAction}>: campos não controlados voltavam ao defaultValue após qualquer action assentada (sucesso ou erro) do React 19.
- WizardStepFormChrome alterado uma vez (cobre 3 wizard steps)
- 15 forms recebem dispatch manual arquivo a arquivo
- AdvisorPasswordResetButton (2 micro-forms sem campo digitável) ficou como está
- Unit spec novo campaignWizardStepFormChrome.unit.spec.tsx
- CHANGELOG-AGENTS.md cleaned (98 duplicates removed)
Also fixes:
- campaignPageActor writable gate only blocks advisors (was blocking coordinators too)
- campaignLeaderships e2e stability (waitForLoadState before attribute assertion)
11 KiB
Infrastructure layer (AGENTS layer — load when touching deploy, CI, or local dev)
Deploy & CI
Production is self-hosted on the homeserver (jorgesolla1313.com.br, behind the Cloudflare tunnel; container/database teqo-1313/teqo_1313). There is no Vercel deploy anymore — the Vercel deploy job, vercel-promote.yml, the Vercel scripts/vercel.json and the VERCEL_* repo secrets were removed in OPS50 (2026-08-17). CI/PR/deploy live on GitHub Actions (OPS71, 2026-08-19); the ISSUE TRACKER stays on the Forgejo (git.solla.dev/fsolla/teqo — labels, claims, pnpm issue/agent:* and the post-merge flips all keep talking to the Forgejo API; GitHub is only the Actions host). The PR CI (ci-pr.yml, job único checks — OPS62) runs the same cascade as always (guards → lint → format → typecheck → knip → cycles → unit → int → build → e2e selected/afetado, skips via ci-scope.mjs); its check-run CI (PR) / checks is the required check of main. There is NO main verifier (ci.yml) anymore: publishing is a manual workflow_dispatch (.github/workflows/deploy.yml) with two jobs — verify (hosted ubuntu-latest, suíte full incl. e2e full) → deploy (needs: [verify], runs-on: [self-hosted, homeserver]): the GitHub self-hosted runner ON the homeserver executes scripts/deploy-homeserver.sh locally (no SSH; outbound-only runner, works behind the tunnel; the hosted job never touches the homeserver). The remote Docker deployment is gone; the production container runs the monolith directly. The Docker Compose file is used ONLY for local dev databases. The run-deploy.yml (OPS38/OPS46) was deleted in OPS71. There is no run-deploy.yml or equivalent in GitHub Actions — deploy is deploy.yml only.
Guarding pnpm push (OPS24+): every PR push goes through scripts/git-push.mjs, which runs ensure-repo-deps (install + Husky in worktrees — OPS51), then gate:push (the pre-push hook), which chains gate:ci — a serialized mirror of the CI checks job (without e2e: OPS72 moved e2e to an explicit skill step) — plus the docs-guards (changelog append-only, aggregation sync, conflict markers — OPS63; the rewrite escape stays in CI only). The hook blocks git push on failure; escape: git push --no-verify (WIP only). There is no YAML push gate — the hook is the gate. CI red on your PR is yours, even pre-existing or infra — "out of feature scope" is not a stop condition (policy "Dono do PR, dono do CI" in docs/AGENT-OPS.md).
Database & local development
- Local dev runs against a local Docker Postgres (
pnpm db:start). To copy production content locally, usepnpm db:pull(scripts/db-pull.mjs): it only reads prod, only writes local, and excludes supporter PII (contact/signature/subscriptionrow data). pnpm db:doctordiagnoses local database connectivity for the dev AND test targets: it names who holds the port, points at exited/crash-looping containers, and flags Postgres containers sharing one data volume (two postmasters on one PGDATA corrupt the WAL — this destroyed the local cluster's checkpoint on 2026-07-25; the canonical setup is ONE compose container on 5432 hostingteqo+teqo_test).pnpm devruns the same preflight viaguard-dev-dband refuses to start with a named remedy instead of a later ECONNREFUSED.db:start/db:stoppin the compose project with-p teqo, so they always target that ONE container even from a worktree directory.db:startis idempotent (OPS37): withteqo-postgres-1up and healthy it is a no-op — a plaincompose upfrom another worktree would recreate the shared container and kill every live dev/e2e connection (the compose file's relative./docker/postgres/initbind mount makes the config hash differ per worktree); apply a real config/image change on purpose withpnpm db:start --force-recreate.- Per-worktree environments (
pnpm worktree next, 2026-08-08; default-go 2026-08-10): parallel agents each get a deterministic isolated env — no fighting over port 3000, the sharedteqo_test, or a second Postgres container.nextclaims BEFORE creating the worktree (OPS33, 2026-08-10): the same queue/order and optimistic lock aspnpm agent:claim(sharedclaimIssueinscripts/lib/agent-github.mjs); a failed claim prints the reason and stops with no orphan worktree;--issue <N>claims the directed issue (ready) or reopens an already-claimed one (in-progress, no re-claim, worktree reuse stays idempotent); the claim brief is printed and the output warns "já claimada — não rodarpnpm agent:claim"(plan/new/killnever touch Issues; the pool's coordinatedclaimIssueForPoolis untouched).worktree next [--issue N] [--stay]provisions: a dev-server port3100+slot(slot = numeric part of the issue code, capped at 999 with hash fallback, bumped on collision among live worktrees), plus dedicated databasesteqo_wt<slot>(dev) andteqo_wt<slot>_test(tests) created on the shared container with the full migration chain AND the minimal seed applied (pnpm migrate+pnpm db:seed:minimalper database — OPS28 parity: CI runs the same pair before e2e, so a worktree'spnpm test:e2emirrors CI instead of failing on seed-dependent specs; the seed is idempotent and local-guarded, and--no-migrateskips both); writes.env.local+.env.test.local(gitignored, marker# teqo worktree env (generated by pnpm worktree next); manual envs are never overwritten) and copiesPAYLOAD_SECRET/S3_*(all-or-nothing)/VAPID from the main repo — plus the gitignored.opencode/secrets/*(e.g.penpot-token, referenced by the worktree'sopencode.jsonMCP config), without which opencode refuses to launch ("bad file reference" for the dangling{file:…}).next/plan/newprintcd <dir>as the last line BY DEFAULT — the caller applies it... (line truncated to 2000 chars) - To (repopulate news content,
pnpm db:seed:postsfetches articles live from jorgesolla.com.br into the LOCAL db (same non-localDATABASE_URLguard aspnpm dev). It writes from a CLI process outside the deployed runtime, so after seeding — or after ANY direct-DB change — you must bust the productionpostscache viaPOST /api/revalidate. See "Posts & Tags" inAGENTS-public.md. - To (re)populate the TSE 2022 election baseline,
pnpm db:seed:tsedownloads TSE open data and imports the Bahia scope into the LOCAL db (same guard). Since the 2026-07-23 hardening, elections-tab reads are cached under theelection-tsetag — after re-seeding a database the deployed app reads, bust it viaPOST /api/revalidate?tag=election-tse. See "Election baseline data" inAGENTS-campaign.md.
Local verification and deploy checklist
- Confirm
DATABASE_URLispostgresql://teqo:teqo@localhost:5432/teqoand.env.testpoints toteqo_test; never use the homeserver prod DB for development, tests, E2E, or local builds. In a provisioned worktree (pnpm worktree next) the values come from its.env.local/.env.test.local(teqo_wt<slot>/teqo_wt<slot>_test, dev port3100+slot) — read them there instead of assuming the main-repo names. - Run
pnpm db:start,pnpm migrate:status, andDATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo_test pnpm migrate:status; every migration must showRan: Yes. - Run
pnpm generate:types,pnpm generate:importmapwhen components changed — com as envsS3_*setadas (valores dummy ok) enquanto o plugins3Storagefor condicional: sem elas o importMap fica órfão doS3ClientUploadHandlere o admin abre em branco em produção (OPS69, P0 em 2026-08-19) —,pnpm exec tsc --noEmit,pnpm lint(zero warnings — the script enforces--max-warnings=0),pnpm format:check(Prettier is the formatting authority;pnpm formatfixes),pnpm exec knip(dead files/dependencies are CI-blocking; delete what your change orphaned),pnpm check:cycles(madge — zero circular imports),pnpm test(unit + int), andpnpm buildagainst the local database. Payload CLI scripts (migrate,generate:*,payload) and the build migrate step use--conditions=react-serversoserver-onlymodules load correctly outside Next.js. Política de e2e (OPS72): o e2e não roda no gate local (gate:push) — é passo explícito da skill (work-issue/agent-work-issue): antes do push o agente roda localmente os e2e que criou + os da mesma superfície afetada — discricionário (o agente decide quais; ferramenta:pnpm test:e2e:affected, espelho local do classifier — gera--no-depspara runs filtrados, S6-FOLLOWUP: no dev mode a cadeia de dependencies dos projects arrasta os arquivos das dependências via closure; filtros manuais locais:pnpm test:e2e --no-deps -- <path>ou--no-deps --project=<família>; limitação #72:--no-deps+ projetos paralelos colide noseedTestUser→--workers=1ou a cadeia padrão de projetos). O CI de PR roda apenas o blast radius (modoselectedviaci-scope.mjs— nuncafull; diff high-risk classificafulle o PR simplesmente não roda e2e). O e2e full roda noverifydo deploy manual (gateia merge e deploy — rede de segurança final antes de publicar).- Never run
knip --fixblind: when knip's module graph is incomplete (it still cannot loadpayload.config.ts— ledgered as P3),--fixdeletes exports that migrations/tests still reference. Verify each removal (e.g.git grep -w <symbol>) before deleting. - Run gate commands bare — never piped (
pnpm test | tailswallows the exit code, and a real failure was masked exactly this way during Pass 2). Large outputs are captured by the terminal; read them from there.
- Never run
- Scan every edited runtime source file with Aikido and resolve new findings before handoff. Do not expand hardening work into unrelated pre-existing findings without an explicit scope change.
- Review production environment variables and migration SQL before deploy;
pnpm buildapplies pending migrations to the deployed database automatically.NEXT_PUBLIC_SITE_URL(ex.:https://jorgesolla1313.com.br) é obrigatória em Production — sem ela,getCampaignInviteBaseURL()lança em runtime e o fluxo de convites/webauthn/link de import da agenda falha com mensagem genérica (C98, 2026-08-09: produção ficou sem a env e "gerar link" do feed quebrava; o preview tinha a de stage). - Mandatory production blocker: do not insert real leadership/support data or enable invites until electoral counsel documents the LGPD art. 11 basis, approves the specific versioned consent text, and an admin creates it with exactly
Consent.key = 'lideranca-autopreenchimento'. The app intentionally fails closed when this key is absent. - Remodel deploy (historical — applied to production 2026-07-23): the destructive remodel chain (
20260721_020109_remodel_municipalities→20260723_200000_remodel_municipalities+20260723_202000_reconcile_municipality_remodel) already ran in production via the Vercel build: campaign vertical reset, 435 municípios seeded, public-site data untouched. Treat any future destructive migration the same way — review its SQL before the first production build that includes it.