diff --git a/.agents/rules/agent-pr-workflow.mdc b/.agents/rules/agent-pr-workflow.mdc index a75bfe4c..2c0353e8 100644 --- a/.agents/rules/agent-pr-workflow.mdc +++ b/.agents/rules/agent-pr-workflow.mdc @@ -37,7 +37,7 @@ When the environment says to use `ManagePullRequest` instead of `gh pr create`: **Use `pnpm push`, never bare `git push`.** Não depende do Husky estar wired: 1. `ensure-repo-deps` — `pnpm install` se `node_modules` faltar/stale + `pnpm run prepare` (Husky) se hooks não estiverem apontando para `.husky` -2. `pnpm gate:push` — espelho do `ci-pr.yml` (inclui `format:check`) +2. `pnpm gate:push` — espelho do `ci-pr.yml` sem e2e (e2e roda uma vez, no CI, após o push; local opcional: `pnpm test:e2e:affected`) 3. `git push --no-verify` — só porque o gate já rodou no passo 2 (não é escape hatch) ```bash diff --git a/.agents/rules/engineering-standards.mdc b/.agents/rules/engineering-standards.mdc index b050ee07..7b8ae662 100644 --- a/.agents/rules/engineering-standards.mdc +++ b/.agents/rules/engineering-standards.mdc @@ -11,7 +11,7 @@ Established 2026-07-23 by the engineering-standards hardening (tracker: `docs/IM 1. **Dead code dies immediately.** After your edits, check what your change orphaned and delete it in the same delivery: run `pnpm exec knip` (files/dependencies are CI-blocking errors; unused exports are warnings being driven to zero). Never leave commented-out code, unused exports, superseded components, or one-shot scripts behind — git history is the archive. 2. **Zero warnings.** `pnpm lint` runs with `--max-warnings=0`. Deliberate placeholders use a `_` prefix. Never silence a rule without an inline justification comment. -3. **Gate em duas velocidades (desde 2026-07-30, paradigma de agentes paralelos — `docs/AGENT-OPS.md`).** **Fast gate local do agente** (iteração durante a sessão): `pnpm gate:fast` (lint + typecheck + test:unit). **Push canônico:** `pnpm push` — `ensure-repo-deps` (install + Husky em worktrees) + `git push`; o hook pre-push roda `gate:push` → `gate:ci` (espelho serial do `ci-pr.yml`, inclui `format:check`). Não use `git push` nu. Fonte única: scripts `gate:*` e `scripts/git-push.mjs` em `package.json`; escape para WIP: `git push --no-verify`. Migrations NÃO são serializadas entre PRs: a cadeia é validada por `pnpm migrate` + `test:int` (incl. `campaignMigrationReconciliation`) em todo PR e na main; rebase antes de `migrate:create` continua obrigatório (o antigo `migration-lock` foi removido em 2026-08-12 — deadlock de CI). Entrega = merge em `main`; deploy gated pelo verificador `ci.yml`. **CI vermelho no teu PR é teu, mesmo pré-existente ou de infra** — "fora do escopo da feature" não é critério de parada (política "Dono do PR, dono do CI" em `docs/AGENT-OPS.md`). +3. **Gate em duas velocidades (desde 2026-07-30, paradigma de agentes paralelos — `docs/AGENT-OPS.md`).** **Fast gate local do agente** (iteração durante a sessão): `pnpm gate:fast` (lint + typecheck + test:unit). **Push canônico:** `pnpm push` — `ensure-repo-deps` (install + Husky em worktrees) + `git push`; o hook pre-push roda `gate:push` → `gate:ci` (espelho serial do `ci-pr.yml` **sem e2e** — OPS59: o e2e roda **uma vez, no CI**, em PR e na main; local opcional para feedback antecipado: `pnpm test:e2e:affected`). Não use `git push` nu. Fonte única: scripts `gate:*` e `scripts/git-push.mjs` em `package.json`; escape para WIP: `git push --no-verify`. Migrations NÃO são serializadas entre PRs: a cadeia é validada por `pnpm migrate` + `test:int` (incl. `campaignMigrationReconciliation`) em todo PR e na main; rebase antes de `migrate:create` continua obrigatório (o antigo `migration-lock` foi removido em 2026-08-12 — deadlock de CI). Entrega = merge em `main`; deploy gated pelo verificador `ci.yml`. **CI vermelho no teu PR é teu, mesmo pré-existente ou de infra** — "fora do escopo da feature" não é critério de parada (política "Dono do PR, dono do CI" em `docs/AGENT-OPS.md`). 4. **Run gate commands bare, never piped** (`pnpm test | tail` swallows the exit code). Never run `knip --fix` blind — verify each removal with `git grep -w ` first (knip still cannot load `payload.config.ts`, so its graph is incomplete; ledgered as P3). ## Type honesty diff --git a/AGENTS.md b/AGENTS.md index d5d57534..4232408c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -158,7 +158,7 @@ The artifact is at **v3**: v2 added E8's `campoFederalVotesByYear` / `federalTal 1. Confirm `DATABASE_URL` is `postgresql://teqo:teqo@localhost:5432/teqo` and `.env.test` points to `teqo_test`; never use Neon 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` / `teqo_wt_test`, dev port `3100+slot`) — read them there instead of assuming the main-repo names. 2. Run `pnpm db:start`, `pnpm migrate:status`, and `DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo_test pnpm migrate:status`; every migration must show `Ran: Yes`. -3. Run `pnpm generate:types`, `pnpm generate:importmap` when components changed, `pnpm exec tsc --noEmit`, `pnpm lint` (zero warnings — the script enforces `--max-warnings=0`), `pnpm format:check` (Prettier is the formatting authority; `pnpm format` fixes), `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), `pnpm test:e2e` (or `pnpm test:all`), and `pnpm build` against the local database. Payload CLI scripts (`migrate`, `generate:*`, `payload`) and the build migrate step use `--conditions=react-server` so `server-only` modules load correctly outside Next.js. +3. Run `pnpm generate:types`, `pnpm generate:importmap` when components changed, `pnpm exec tsc --noEmit`, `pnpm lint` (zero warnings — the script enforces `--max-warnings=0`), `pnpm format:check` (Prettier is the formatting authority; `pnpm format` fixes), `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), and `pnpm build` against the local database. Payload CLI scripts (`migrate`, `generate:*`, `payload`) and the build migrate step use `--conditions=react-server` so `server-only` modules load correctly outside Next.js. **O e2e não roda mais no gate local (OPS59):** ele roda **uma vez, no CI** (PR e main — gateia merge e deploy); local opcional para feedback antecipado: `pnpm test:e2e:affected`. - Never run `knip --fix` blind: when knip's module graph is incomplete (it still cannot load `payload.config.ts` — ledgered as P3), `--fix` deletes exports that migrations/tests still reference. Verify each removal (e.g. `git grep -w `) before deleting. - Run gate commands bare — never piped (`pnpm test | tail` swallows 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. 4. 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. diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md index ef3c8078..10e3f52d 100644 --- a/docs/CHANGELOG-AGENTS.md +++ b/docs/CHANGELOG-AGENTS.md @@ -22,6 +22,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente **Recently resolved (2026-08-18):** **OPS57** — o safety net do `forgejo-pr-automerge` agora cobre **todas as PRs same-repo para `main`**, não só `cursor/*`. Motivação (débito deferido na OPS56): dos 25 PRs mergeados no repo, **zero** eram `cursor/*` — 9 de `plans/*`, 11 de worktrees humanos, 5 de `refs/pull/N/head` (era da migração) — o filtro antigo nunca cobriu nenhum PR real, e o incidente OPS52 (PR #27, humano, merge falhou silenciosamente) era exatamente a classe excluída. Mudanças: `if:` do job em `agent-pr-ready-automerge.yml` → `state == 'open' && head.repo.full_name == github.repository` (guarda de fork — o repo é público e uma PR de fork mergeada dispararia o deploy de prod; campos já usados no payload, sem dependência de `is_draft` do webhook); política de draft movida para o CLI `forgejo-pr-automerge.mjs` com o head ref do **re-read da API** — draft `cursor/*` → Ready (pool inalterado); draft não-`cursor/*` → skip exit 0 (**draft = veto do ator, PR humana nunca é forçada**); PRs ready não-`cursor/*` (humanas/plans) passam a mergear com CI verde — redundante no caminho feliz (o fluxo humano já arma `--auto --rebase`) e é o que pega a classe OPS52; `ready_for_review` em PR draft re-dispara o job e mergea. Docs sincronizadas (`docs/AGENT-OPS.md` tabela CI por alvo, `.agents/rules/agent-pr-workflow.mdc` seção Safety net). Rejeitado: `!is_draft` no YAML (campo do payload não verificado, falha fail-open) e allowlist de prefixos (códigos humanos sem prefixo comum — OPS/S1/PUB5). A própria PR desta entrega ainda usa o workflow de `main` (antigo); a próxima PR exercita o gatilho alargado. Sem migration/access/Consent/UI. Testes: nenhum novo (lógica de veredito já coberta pelos specs da OPS56; branch de draft é 3 linhas no CLI); gates unit/int verdes. +**Recently resolved (2026-08-18):** **OPS59** — o pre-push local não roda mais e2e: ele roda **uma vez, no CI** (PR e main — gateia merge e deploy). O espelho `scripts/gate-ci.mjs` cortou o bloco e2e da cascata (playwright install, build `.next-e2e`, `test:e2e`) e o preflight de DB passou a depender só de test/build; o readout do classificador mantém `e2e=` e imprime o que o CI rodará ("e2e runs in CI, not in this gate — OPS59"), com o check `unmapped` duplicado removido (`ci-scope.mjs` já o imprime no stderr). `package.json`, `.husky/pre-push`, `git-push.mjs` e os workflows `.forgejo/*` intocados (CI verificado, não alterado: `ci-pr.yml` e2e → `checks`; `ci.yml` e2e → `checks` → `deploy`). `tests/unit/ciSkipInvariants.unit.spec.ts` tirou `gate-ci.mjs` da lista de arquivos que devem conter `.next-e2e` (o gate local não builda mais e2e; `run-e2e-affected.mjs`/`playwright.config.ts` seguem cobertos — e2e local opcional intacto). Docs sincronizadas: `AGENTS.md` (checklist local item 3 — `test:e2e`/`test:all` saem da obrigatória; "e2e = CI, local opcional `pnpm test:e2e:affected`"), `.agents/rules/engineering-standards.mdc` e `agent-pr-workflow.mdc` (gate descrito como "espelho do ci-pr.yml sem e2e"). Verificado por grep: `AGENT-OPS.md` e skills (`work-issue`, `agent-work-issue`, `worktree-next-issue`, `local-database`) não citavam e2e no pre-push — sem mudança. Anti-goals respeitados: sem flag de "pular e2e" (bypass continua só via `--no-verify` documentado), política "Dono do PR, dono do CI" inalterada. Sem migration/access/Consent/UI. Verificação: `pnpm gate:ci` local (cascata nova full — lint/format/typecheck/knip/cycles/unit/int/build em `teqo_test`; e2e vira print), unit 2286+ verdes; o e2e desta PR roda full no CI por HIGH_RISK (`gate-ci.mjs`). + **Recently resolved (2026-08-17):** **OPS51** — o banco de produção saiu do Neon para o Postgres do homeserver (`teqo_1313`, container `teqo-1313`), sem mudança de config: o `~/stack/teqo-1313.env` já apontava para o banco preparado (criado 16/08 com role própria + cadeia completa de 56 migrations + zero conteúdo — verificado), então restaurar = a produção "apareceu" no container. **Divergência deliberada da intenção (questão A/B validada como B):** rejeitado criar `teqo_prod` novo — o argumento "o banco do 1313 tem vida própria" não se sustenta (o site 1313 É a produção deste repo) e o OPS53 já trata `teqo_1313` como produção. Execução 100% no homeserver (PII nunca tocou a workstation): URL do Neon puxada do Vercel (`vercel env pull`, projeto `jorgesolla`, `DATABASE_URL_UNPOOLED` → `NEON_DATABASE_URL` em `~/stack/.env` chmod 600), `pg_dump -Fc` (Neon PG 17.10) → `/srv/hdd/backups/teqo-neon-pre-migracao/teqo-neon-full-20260817-204800.dump` (5,8 MB, sha256 `f12196f2…`), `pg_restore --no-owner --no-privileges` como a role `teqo_1313` (drop schema + grant, padrão do `db-pull`), verificado: contagens por tabela idênticas ao Neon (1919 contatos, 39 posts, 1487 assinaturas, 1484 subscrições, 35 campaign_users, 392 lideranças, 2 pledges, 435 municípios, 6 atividades; supporter segue 0 — blocker LGPD ainda de pé), sequences no topo (`last_value == max(id)`), objetos com dono `teqo_1313`, `payload migrate` no-op (56 = 56, serviço de maintenance `teqo-1313-migrate`), 3 credenciais WebAuthn + 34 sessions + 5 chaves Consent restauradas (RP ID não mudou — biometria segue valendo). Smoke sem deploy: `/`, artigo com conteúdo restaurado, `/campanha/login`, `/admin`, `POST /api/revalidate` (`posts` + `global_privacy-policy`), WebAuthn `login-options`, barreira 307 sem sessão — verdes; smoke de browser (login, biometria, convites) validado manualmente. **Rollback documentado:** Neon nunca foi escrito (só lido); voltar = re-apontar `DATABASE_URL` do `teqo-1313.env` para o Neon + `compose up -d`. **Débito registrado (→ OPS52):** as 40 rows de `media` têm URLs relativas (`/api/media/file/…`) sem arquivos em lugar nenhum — capas dão 500 no homeserver e **404 no próprio Vercel/produção atual** (estado pré-existente; migração manteve paridade). Repo: plano da intenção concluído, `AGENTS.md` e help de `db:pull`/`check-push-chain` sem a referência ao Neon; infra-solla `STATE.md` atualizado à parte. **Recently resolved (2026-08-17):** **OPS53** — o deploy do site 1313 virou etapa do CI: mergir em `main` com a suite verde == site atualizado em `jorgesolla1313.com.br`, sem passo manual. O job `deploy` em `.forgejo/workflows/ci.yml` roda no host do runner (workstation, `runs-on: host`) e streama `scripts/deploy-homeserver.sh` para o homeserver via SSH (`ssh homeserver "bash -s -- "`); o script remoto (bash puro, homeserver sem dependências novas) faz: guard de HEAD (`git ls-remote` de `main` == SHA do job, senão skip "stale"), `flock` de serialização, workspace `~/teqo-deploy` (clone do Forgejo local), `docker login localhost:5000` (REGISTRY_USER/PASSWORD de `~/stack/.env`), build dos stages `runner`+`migrator` no homeserver (`--network stack_default` para a geração estática alcançar o `postgres:5432` do compose; secrets de `~/stack/teqo-1313.env` via BuildKit, sem camadas/logs), push no registry local, swap dos tags de imagem no compose (com backup `docker-compose.yml.pre-`), **migrate pelo serviço de maintenance `teqo-1313-migrate` antes do rollout**, `docker compose up -d`, espera do healthcheck e smoke (`/`, `/campanha/login`, `/admin`, barreira 307, WebAuthn `login-options` com body JSON, `POST /api/revalidate` com o secret real — rehearsal validado contra o site vivo). Falha pós-swap → rollback automático best-effort (restore do backup + `up -d` da imagem anterior) + job vermelho. **Divergência deliberada da intenção (fato novo):** a intenção assumia o runner no homeserver; verificado (`infra-solla/STATE.md` + ao vivo) que o runner é a `workstation-runner` na workstation e o stack de produção vive no homeserver — a abordagem A foi corrigida para "job `runs-on: host` na workstation + todo o deploy no homeserver via SSH", o que manteve **zero secrets novos no Forgejo** e zero mudança de infra no compose. Nenhum secret no repo; envs seguem chmod-600 no homeserver. Runbook (rollback manual, falhas conhecidas, decisão de segurança do host label): `docs/ops/teqo-1313-deploy.md` (novo). Guarda estrutural nova: `tests/unit/deployScript.unit.spec.ts` (bash -n + invariantes do pipeline: HEAD guard, BuildKit secrets, ordem migrate→rollout, smoke). AGENTS.md/AGENT-OPS.md atualizados. Primeiro deploy real = o merge deste próprio item (tracer bullet); infra-solla `STATE.md` anotado à parte. diff --git a/docs/changelog/2026-08-18-ops59.md b/docs/changelog/2026-08-18-ops59.md new file mode 100644 index 00000000..fba40c12 --- /dev/null +++ b/docs/changelog/2026-08-18-ops59.md @@ -0,0 +1 @@ +**Recently resolved (2026-08-18):** **OPS59** — o pre-push local não roda mais e2e: ele roda **uma vez, no CI** (PR e main — gateia merge e deploy). O espelho `scripts/gate-ci.mjs` cortou o bloco e2e da cascata (playwright install, build `.next-e2e`, `test:e2e`) e o preflight de DB passou a depender só de test/build; o readout do classificador mantém `e2e=` e imprime o que o CI rodará ("e2e runs in CI, not in this gate — OPS59"), com o check `unmapped` duplicado removido (`ci-scope.mjs` já o imprime no stderr). `package.json`, `.husky/pre-push`, `git-push.mjs` e os workflows `.forgejo/*` intocados (CI verificado, não alterado: `ci-pr.yml` e2e → `checks`; `ci.yml` e2e → `checks` → `deploy`). `tests/unit/ciSkipInvariants.unit.spec.ts` tirou `gate-ci.mjs` da lista de arquivos que devem conter `.next-e2e` (o gate local não builda mais e2e; `run-e2e-affected.mjs`/`playwright.config.ts` seguem cobertos — e2e local opcional intacto). Docs sincronizadas: `AGENTS.md` (checklist local item 3 — `test:e2e`/`test:all` saem da obrigatória; "e2e = CI, local opcional `pnpm test:e2e:affected`"), `.agents/rules/engineering-standards.mdc` e `agent-pr-workflow.mdc` (gate descrito como "espelho do ci-pr.yml sem e2e"). Verificado por grep: `AGENT-OPS.md` e skills (`work-issue`, `agent-work-issue`, `worktree-next-issue`, `local-database`) não citavam e2e no pre-push — sem mudança. Anti-goals respeitados: sem flag de "pular e2e" (bypass continua só via `--no-verify` documentado), política "Dono do PR, dono do CI" inalterada. Sem migration/access/Consent/UI. Verificação: `pnpm gate:ci` local (cascata nova full — lint/format/typecheck/knip/cycles/unit/int/build em `teqo_test`; e2e vira print), unit 2286+ verdes; o e2e desta PR roda full no CI por HIGH_RISK (`gate-ci.mjs`). diff --git a/docs/plans/gate-push-sem-e2e-impl.md b/docs/plans/gate-push-sem-e2e-impl.md new file mode 100644 index 00000000..7781f43a --- /dev/null +++ b/docs/plans/gate-push-sem-e2e-impl.md @@ -0,0 +1,75 @@ +# Impl: Pre-push sem e2e: e2e roda uma vez, no CI (gate local fica com os outros testes) + +Status: aprovado +Atualizado em: 2026-08-18 +Issue: #46 +Intenção: docs/plans/gate-push-sem-e2e.md +Appetite restante: ~0,5 dia eng (herdado — cabe com folga) + +## Leitura da intenção + +- **Outcome:** `pnpm push` / `.husky/pre-push` não rodam mais e2e (sem playwright install, sem build `.next-e2e`, sem `test:e2e`); mantêm lint, format:check, typecheck, knip, check:cycles, test:unit, test:int e build na cascata/escopo atuais. O e2e do CI continua como está e gateia PR (job `e2e` do `ci-pr.yml` → rollup `checks`) e main (`e2e` do `ci.yml` → `checks` → `deploy`). Docs do fluxo atualizadas para "e2e = verificado no CI após o push; local opcional (`pnpm test:e2e:affected`)". +- **O que NÃO negociar:** CI intocado (não remover/alterar e2e de PR ou main); nenhum flag novo de "pular e2e" (escape `--no-verify` documentado continua sendo o único bypass); política "Dono do PR, dono do CI" inalterada. +- **O que reavaliar:** a hipótese de "áreas prováveis" aponta só para `scripts/gate-ci.mjs` + docs. Verificado no código: além do bloco e2e de `gate-ci.mjs`, existe **um teste unit que ancora o e2e no gate local** (`ciSkipInvariants` lista `scripts/gate-ci.mjs` entre os arquivos que devem conter `.next-e2e`) — a remoção do bloco quebra esse teste se ele não for ajustado junto. Também verificado: `AGENTS.md` (checklist local item 3) cita `pnpm test:e2e` como passo local obrigatório — precisa virar "e2e no CI; local opcional". `AGENT-OPS.md`, `work-issue`/`agent-work-issue`/`worktree-next-issue`/`local-database` skills **não** citam e2e no pre-push (verificado por grep) — nada a mudar nelas. Workflows do CI: e2e → `checks` (PR) e e2e → `checks` → `deploy` (main) comprovados — verificação, sem mudança. + +## Abordagem recomendada + +```mermaid +flowchart LR + P[push / pre-push] --> G[gate:push = gate:ci] + G --> F1[fase 1: lint format typecheck knip cycles unit] + F1 --> F2[fase 2: preflight teqo_test migrate seed int build] + F2 -. e2e: print informativo .-> CI[CI: job e2e PR + main] +``` + +**Opções consideradas:** + +- **A — Editar o espelho (`scripts/gate-ci.mjs`):** remover o bloco e2e da cascata, manter o readout do classificador como aviso informativo ("o que o CI fará"), ajustar o invariante `ciSkipInvariants` e atualizar os 3 docs que descrevem o gate. +- B — Flag/env `--skip-e2e` no gate para o agente optar por pular: vira o bypass um recurso (anti-goal explícito da intenção). +- C — Novos scripts de gate (ex. `gate:push` sem e2e + `gate:ci` completo para quem quiser): twin do espelho, duplicação de cascata, viola "edit the owner, don't twin". + +**Recomendação:** A — a intenção manda cortar o bloco do espelho, não criar caminho paralelo; `gate:push`/`gate:ci`/`.husky/pre-push`/`git-push.mjs` continuam como a cadeia única (sem mudança em package.json, hook ou git-push.mjs). +**Rejeitadas:** B porque cria o bypass como feature; C porque duplica a cascata que o espelho existe para manter DRY. + +### Componentes / mudanças + +- **`scripts/gate-ci.mjs`** — corta o bloco e2e (linhas ~146–166: playwright install, migrate/seed de e2e, build `.next-e2e`, `test:e2e`); o e2e sai de `needsDb` (preflight passa a depender só de test/build); o readout do escopo mantém `e2e=${scope.e2e.mode}` e o bloco vira **print informativo** do que o CI rodará (mode `full`/`selected`/`none` + specs) com a nota "e2e roda no CI — não no pre-push (OPS59)"; remove o check `unmapped` duplicado (o próprio `ci-scope.mjs` já imprime os unmapped no stderr antes do parse — e sem e2e local ele virou puramente informativo); docstring atualizada (espelho do `ci-pr.yml` **sem e2e** + corrige o path `.github/workflows/ci-pr.yml` → `.forgejo/workflows/ci-pr.yml`, obsoleto desde OPS50). Linha final do sucesso: "ci-pr mirror without e2e (e2e verified in CI)". +- **`tests/unit/ciSkipInvariants.unit.spec.ts`** — tira `scripts/gate-ci.mjs` da lista do invariante `.next-e2e` (o invariante existe para manter artefatos de build e2e fora do dist de dev; o gate local não builda mais e2e). `scripts/run-e2e-affected.mjs` e `playwright.config.ts` continuam na lista — o e2e local opcional e o CI ainda usam `.next-e2e`. +- **`.agents/rules/engineering-standards.mdc`** (linha do gate em duas velocidades) — "gate:push → gate:ci (espelho serial do ci-pr.yml, inclui format:check)" ganha a cláusula OPS59: espelho **sem e2e**; e2e roda uma vez no CI (PR e main); local opcional via `pnpm test:e2e:affected`. +- **`.agents/rules/agent-pr-workflow.mdc`** (passo 2 do push canônico) — idem: "espelho do ci-pr.yml (inclui format:check)" → "sem e2e (e2e é verificado pelo CI após o push; local opcional `pnpm test:e2e:affected`)". +- **`AGENTS.md`** (checklist local item 3) — sai `pnpm test:e2e`/`pnpm test:all` da sequência obrigatória local; entra "e2e roda no CI (PR e main) — local opcional `pnpm test:e2e:affected`". +- **`docs/plans/gate-push-sem-e2e.md`** (intenção) — Status → `entregue` no fechamento (padrão dos planos entregues, ex. `gate-push-local.md`). +- **`docs/changelog/2026-08-18-ops59.md`** + `pnpm changelog:build` (OPS44, agregado insert-only). +- **Migration:** sem migration. **Access / Consent:** N/A. **UI:** N/A (Impeccable A). + +### Dados → forma (se aplicável) + +N/A — chore de DX/processo, sem superfície de dados. + +## Fases verificáveis + +1. **Gate + invariante** — `scripts/gate-ci.mjs` (corte do bloco e2e + readout informativo) e `tests/unit/ciSkipInvariants.unit.spec.ts`. Verificação: `pnpm test:unit -- tests/unit/ciSkipInvariants.unit.spec.ts` e inspeção do diff do gate. +2. **Docs do fluxo** — engineering-standards.mdc, agent-pr-workflow.mdc, AGENTS.md, changelog OPS44, status da intenção. Verificação: varredura final por citações residuais (`gate:push`/`gate:ci`/`test:e2e`/`test:all` nas rules/skills/AGENT-OPS — nada deve descrever e2e como etapa do pre-push). +3. **Gates** — `pnpm gate:ci` local (roda a cascata nova: fase 1 + migrate/seed/int/build em `teqo_test`; e2e vira print) + `pnpm push`. O e2e em si NÃO roda local — por design; o CI roda full nesta PR (`gate-ci.mjs` é HIGH_RISK → `test`/`e2e` full no ci-pr). + +## Rabbit holes / Não escopo (engenharia) + +- Remodelar a cascata do espelho (ex.: "já que removo e2e, simplifico o build/preflight") — corte só o bloco e2e; o restante fica fiel ao CI. +- Flag/env de pular e2e no gate — bypass como recurso, anti-goal. +- Tocar nos workflows `.forgejo/*` — CI é o verificado, intocado (verificação apenas). +- Mudar `scripts/run-e2e-affected.mjs`/`e2e-affected.mjs`/manifestos — o e2e local opcional permanece como está. +- Tirar `.next-e2e` de gitignore/prettierignore/playwright.config — ainda usado pelo CI e pelo e2e local opcional. +- Editar `docs/plans/gate-push-local.md` (histórico entregue — não é lock, é registro). + +## Riscos e mitigação + +- **Invariante CI quebra ao remover o bloco** → `ciSkipInvariants` atualizado na mesma mudança (fase 1 junto do gate); a suíte unit roda antes do push. +- **Citação residual de e2e no pre-push em doc** → varredura explícita na fase 2 (regras/skills/AGENT-OPS/AGENTS.md) antes do push; `engineering-standards.mdc` e `agent-pr-workflow.mdc` são a fonte contratual do gate. +- **Gate local fica "mais fraco" que o CI por engano (skips divergentes)** → o restante da cascata e os skips do classificador ficam intactos; o readout do escopo (incl. e2e) é impresso para o agente saber o que o CI fará. +- **Docs-only diff sem e2e no CI**: comportamento pré-existente (classificador pula e2e em docs-only) — nada muda. + +## Aceite de engenharia + +- [x] Aceite de produto da intenção ainda coberto (push sem e2e; CI gateando PR e main — workflows verificados: `ci-pr.yml` e2e → `checks`; `ci.yml` e2e → `checks` → `deploy`) +- [x] Invariantes AGENTS/engineering-standards (nenhum write de dado; docs em pt-BR; sem acesso/Consent/migration) +- [x] Testes previstos: `ciSkipInvariants` ajustado; `pnpm gate:ci` valida a cascata nova; CI roda full (HIGH_RISK `gate-ci.mjs`) diff --git a/docs/plans/gate-push-sem-e2e.md b/docs/plans/gate-push-sem-e2e.md index 767a5b66..c27d8be1 100644 --- a/docs/plans/gate-push-sem-e2e.md +++ b/docs/plans/gate-push-sem-e2e.md @@ -1,6 +1,6 @@ # Pre-push sem e2e: e2e roda uma vez, no CI (gate local fica com os outros testes) -Status: rascunho +Status: entregue (2026-08-18) Atualizado em: 2026-08-18 Issue: #46 Priority: P2 diff --git a/scripts/gate-ci.mjs b/scripts/gate-ci.mjs index 4d8e34f9..726a1add 100644 --- a/scripts/gate-ci.mjs +++ b/scripts/gate-ci.mjs @@ -1,8 +1,10 @@ #!/usr/bin/env node /** - * Local mirror of `.github/workflows/ci-pr.yml` (serial): phase-1 cheap checks, - * then phase-2 expensive (int/build/e2e). Skips align with `scripts/ci-scope.mjs` - * (`origin/main` by default). + * Local mirror of `.forgejo/workflows/ci-pr.yml` (serial): phase-1 cheap checks, + * then phase-2 expensive (int/build). e2e is NOT part of the local mirror + * (OPS59): it runs once, in CI (PR job `e2e` → `checks`; main `ci.yml` e2e → + * `checks` → `deploy`). Local optional feedback: `pnpm test:e2e:affected`. + * Skips align with `scripts/ci-scope.mjs` (`origin/main` by default). * * Preflight checks only `teqo_test` (ci-pr never touches the dev DB). Escape * during pipeline cutover: `git push --no-verify` (documented in AGENT-OPS). @@ -71,11 +73,8 @@ const main = async () => { const scope = readJsonScript('scripts/ci-scope.mjs') console.log( - `[gate:ci] scope base=${scope.base} code=${scope.code.mode} build=${scope.build.mode} test=${scope.test.mode} e2e=${scope.e2e.mode}`, + `[gate:ci] scope base=${scope.base} code=${scope.code.mode} build=${scope.build.mode} test=${scope.test.mode} e2e=${scope.e2e.mode} (e2e runs in CI, not in this gate — OPS59)`, ) - if (scope.e2e.unmapped?.length > 0) { - console.error(`[gate:ci] e2e unmapped src/ paths:\n ${scope.e2e.unmapped.join('\n ')}`) - } // --- Phase 1 (cheap) --- run('check-test-locations', 'node', ['scripts/check-test-locations.mjs']) @@ -105,8 +104,7 @@ const main = async () => { } // --- Phase 2 (expensive) — only when phase 1 would have been green --- - const needsDb = - scope.test.mode !== 'none' || scope.build.mode !== 'none' || scope.e2e.mode !== 'none' + const needsDb = scope.test.mode !== 'none' || scope.build.mode !== 'none' if (needsDb) { console.log('\n[gate:ci] ▶ preflight (teqo_test)') const dbOk = await diagnoseDatabaseTarget({ @@ -144,28 +142,14 @@ const main = async () => { } if (scope.e2e.mode === 'none') { - console.log('\n[gate:ci] ⊘ e2e skipped (no e2e blast radius)') + console.log('\n[gate:ci] ⊘ e2e: CI will skip (no e2e blast radius)') } else { - run('playwright install chromium', 'pnpm', ['exec', 'playwright', 'install', 'chromium']) - run('e2e migrate', 'pnpm', ['migrate'], dbEnv) - run('e2e seed:minimal', 'pnpm', ['db:seed:minimal'], dbEnv) - run('e2e build', 'pnpm', ['build'], { ...dbEnv, NEXT_DIST_DIR: '.next-e2e' }) - - const e2eEnv = { ...dbEnv, E2E_PROD: '1', CI: '1', NEXT_DIST_DIR: '.next-e2e' } - if (scope.e2e.mode === 'full') { - run('test:e2e (full)', 'pnpm', ['test:e2e'], e2eEnv) - } else { - const specPaths = scope.e2e.specs.map((name) => `tests/e2e/${name}.e2e.spec.ts`) - run( - `test:e2e (selected: ${scope.e2e.specs.join(', ')})`, - 'pnpm', - ['test:e2e', '--', ...specPaths], - e2eEnv, - ) - } + const specs = + scope.e2e.mode === 'full' ? 'full suite' : `selected: ${scope.e2e.specs.join(', ')}` + console.log(`\n[gate:ci] ▶ e2e: runs in CI (job e2e, ${specs}) — not in this gate (OPS59)`) } - console.log('\n[gate:ci] ✓ all checks passed (ci-pr mirror, cascade + affected scope)') + console.log('\n[gate:ci] ✓ all checks passed (ci-pr mirror without e2e — e2e verified in CI)') } await main() diff --git a/tests/unit/ciSkipInvariants.unit.spec.ts b/tests/unit/ciSkipInvariants.unit.spec.ts index d8b97dd0..19ea540f 100644 --- a/tests/unit/ciSkipInvariants.unit.spec.ts +++ b/tests/unit/ciSkipInvariants.unit.spec.ts @@ -83,7 +83,6 @@ describe('ciSkipInvariants', () => { '.prettierignore', 'eslint.config.mjs', 'playwright.config.ts', - 'scripts/gate-ci.mjs', 'scripts/run-e2e-affected.mjs', 'tsconfig.json', ]) {