mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
review(OPS88): simplify — build surface do reset, contrato high-risk, erros rotulados, docs canônicos
This commit is contained in:
parent
9fb77e2e25
commit
99b36374c8
13 changed files with 69 additions and 47 deletions
6
.github/workflows/ci-pr.yml
vendored
6
.github/workflows/ci-pr.yml
vendored
|
|
@ -155,7 +155,7 @@ jobs:
|
|||
- name: Unit tests (changed since PR base, full on empty selection)
|
||||
if: steps.scope.outputs.test_mode == 'changed'
|
||||
run: node scripts/vitest-changed-or-full.mjs --suite unit --base origin/${{ github.base_ref }}
|
||||
- name: Prepare database URL (int service)
|
||||
- name: Prepare database URL
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo_test" >> "$GITHUB_ENV"
|
||||
|
|
@ -163,9 +163,9 @@ jobs:
|
|||
# must win through the .env.test.local layer (same mechanism
|
||||
# `pnpm worktree next` uses).
|
||||
echo "DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo_test" > .env.test.local
|
||||
- name: Migrate (int database)
|
||||
- name: Migrate
|
||||
run: pnpm migrate
|
||||
- name: Seed minimal (int database)
|
||||
- name: Seed minimal
|
||||
run: pnpm db:seed:minimal
|
||||
|
||||
- name: Integration tests (full suite)
|
||||
|
|
|
|||
8
.github/workflows/deploy.yml
vendored
8
.github/workflows/deploy.yml
vendored
|
|
@ -86,7 +86,7 @@ jobs:
|
|||
- name: Unit tests (full suite)
|
||||
run: pnpm test:unit
|
||||
|
||||
- name: Prepare database URL (int service)
|
||||
- name: Prepare database URL
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo_test" >> "$GITHUB_ENV"
|
||||
|
|
@ -94,9 +94,9 @@ jobs:
|
|||
# must win through the .env.test.local layer (same mechanism
|
||||
# `pnpm worktree next` uses).
|
||||
echo "DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo_test" > .env.test.local
|
||||
- name: Migrate (int database)
|
||||
- name: Migrate
|
||||
run: pnpm migrate
|
||||
- name: Seed minimal (int database)
|
||||
- name: Seed minimal
|
||||
run: pnpm db:seed:minimal
|
||||
- name: Integration tests (full suite)
|
||||
env:
|
||||
|
|
@ -106,7 +106,7 @@ jobs:
|
|||
VITEST_MAX_WORKERS: 4
|
||||
run: pnpm test:int
|
||||
|
||||
- name: Reset database (single service)
|
||||
- name: Reset database (drop → migrate → seed)
|
||||
# OPS88: one Postgres per run — the e2e build starts from the same
|
||||
# clean baseline (drop schema → migrate → seed:minimal) instead of a
|
||||
# second container. The full suite always runs here, so only the
|
||||
|
|
|
|||
|
|
@ -74,14 +74,14 @@ Arquivos de "lista" que N agentes escrevem em paralelo (plans, migrações, cata
|
|||
|
||||
## CI por alvo (GitHub Actions, OPS71)
|
||||
|
||||
| Workflow | Trigger | Banco | Passos |
|
||||
| ------------------------------------ | --------------------------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ci-pr.yml` | PR → `main` (same-repo) | 2× service Postgres 17 | Job único `checks` sequencial (OPS62 X1): guards → lint→format→typecheck→knip→cycles→unit→int→build→e2e; skips via `ci-scope.mjs` (step `scope`); **fail-fast estrutural** — step vermelho interrompe o job, o run falha; `concurrency` nativo cancela o run em push novo; e2e **apenas blast radius** — `selected` ou, em high-risk, o conjunto `curated` (nunca `full` — OPS72/OPS86); área de risco sem mapping falha `unmapped-risk`; unit/int `changed` com seleção vazia cai em full (`vitest-changed-or-full.mjs`) — em 1 processo com `PLAYWRIGHT_WORKERS=4`; 2 services por nome na rede do job (`postgres-int`/`postgres-build`, sem publish host); fork-PR excluída no `if:` do job (repo público — não queima minutes) |
|
||||
| `deploy.yml` | **`workflow_dispatch` manual** | 2× service Postgres 17 | **Dois jobs:** `verify` (hosted, suíte **full** sem skips — lint→format→typecheck→knip→cycles→unit→int→build→e2e full) → `deploy` (`needs: [verify]`, `runs-on: [self-hosted, homeserver]`) executa `scripts/deploy-homeserver.sh <sha>` **localmente** no homeserver (sem SSH; idempotente; HEAD guard; flock; migrator→migrate→runner→rollout→smoke) |
|
||||
| `issue-done-on-main-merge.yml` | PR merged → `main` (same-repo) + **dispatch manual (recovery)** | — | `Closes`/`Fixes` → `done` + `in-prod` + **close** **no GitHub** (token `GITHUB_TOKEN`; body do PR via `PR_BODY` do evento GitHub — no dispatch, busca via `gh` com o mesmo contrato: merged + main + same-repo) |
|
||||
| `plan-issue-ready-on-main-merge.yml` | PR merged → `main` (same-repo) + **dispatch manual (recovery)** | — | `Related #N` aguardando plano → `ready` **no GitHub** (OPS18; soft-skip; mesmo token) |
|
||||
| `agent-pr-ready-automerge.yml` | PR same-repo → `main` (open/sync/…) | — | Safety net via `github-pr-automerge.mjs` (plain Node, REST+GraphQL): draft `cursor/*` → Ready (PATCH); arm `enablePullRequestAutoMerge` (rebase) **com o `AUTOMERGE_PAT`** (fail-closed — nunca o `GITHUB_TOKEN` nativo, cujo merge como `github-actions[bot]` não cria runs no `closed`; OPS71-FLIP) — o **servidor** espera o required check `CI (PR) / checks` (o OPS64 "rollup pode mentir" é estrutural aqui: job único posta um check-run honesto); draft não-`cursor/*` = veto, skip |
|
||||
| `archive-cursor-agent.yml` | dispatch manual (ops) | — | Helper: cancela run + arquiva um agente Cursor Cloud (`CURSOR_API_KEY`) — **dormente (OPS65)** como o pool |
|
||||
| Workflow | Trigger | Banco | Passos |
|
||||
| ------------------------------------ | --------------------------------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ci-pr.yml` | PR → `main` (same-repo) | 1× service Postgres 17 | Job único `checks` sequencial (OPS62 X1): guards → lint→format→typecheck→knip→cycles→unit→int→build→e2e; skips via `ci-scope.mjs` (step `scope`); **fail-fast estrutural** — step vermelho interrompe o job, o run falha; `concurrency` nativo cancela o run em push novo; e2e **apenas blast radius** — `selected` ou, em high-risk, o conjunto `curated` (nunca `full` — OPS72/OPS86); área de risco sem mapping falha `unmapped-risk`; unit/int `changed` com seleção vazia cai em full (`vitest-changed-or-full.mjs`) — em 1 processo com `PLAYWRIGHT_WORKERS=4`; 1 service `postgres-int` (localhost:5432) com **`pnpm db:reset`** (drop→migrate→seed:minimal) entre int e build/e2e (OPS88 — substitui o 2º container); fork-PR excluída no `if:` do job (repo público — não queima minutes) |
|
||||
| `deploy.yml` | **`workflow_dispatch` manual** | 1× service Postgres 17 | **Dois jobs:** `verify` (hosted, suíte **full** sem skips — lint→format→typecheck→knip→cycles→unit→int→`db:reset`→build `.next-e2e`→e2e full; OPS88: um build só, o default saiu) → `deploy` (`needs: [verify]`, `runs-on: [self-hosted, homeserver]`) executa `scripts/deploy-homeserver.sh <sha>` **localmente** no homeserver (sem SSH; idempotente; HEAD guard; flock; migrator→migrate→runner→rollout→smoke) |
|
||||
| `issue-done-on-main-merge.yml` | PR merged → `main` (same-repo) + **dispatch manual (recovery)** | — | `Closes`/`Fixes` → `done` + `in-prod` + **close** **no GitHub** (token `GITHUB_TOKEN`; body do PR via `PR_BODY` do evento GitHub — no dispatch, busca via `gh` com o mesmo contrato: merged + main + same-repo) |
|
||||
| `plan-issue-ready-on-main-merge.yml` | PR merged → `main` (same-repo) + **dispatch manual (recovery)** | — | `Related #N` aguardando plano → `ready` **no GitHub** (OPS18; soft-skip; mesmo token) |
|
||||
| `agent-pr-ready-automerge.yml` | PR same-repo → `main` (open/sync/…) | — | Safety net via `github-pr-automerge.mjs` (plain Node, REST+GraphQL): draft `cursor/*` → Ready (PATCH); arm `enablePullRequestAutoMerge` (rebase) **com o `AUTOMERGE_PAT`** (fail-closed — nunca o `GITHUB_TOKEN` nativo, cujo merge como `github-actions[bot]` não cria runs no `closed`; OPS71-FLIP) — o **servidor** espera o required check `CI (PR) / checks` (o OPS64 "rollup pode mentir" é estrutural aqui: job único posta um check-run honesto); draft não-`cursor/*` = veto, skip |
|
||||
| `archive-cursor-agent.yml` | dispatch manual (ops) | — | Helper: cancela run + arquiva um agente Cursor Cloud (`CURSOR_API_KEY`) — **dormente (OPS65)** como o pool |
|
||||
|
||||
`agent-pool.yml` foi **removido no OPS65** (pool dormente; o tick `*/10` custava 144 `pnpm install`/dia na workstation). O verificador de main (`ci.yml`) foi **eliminado no OPS71** (gate 2026-08-19): só o `verify` do dispatch manual roda a suíte full.
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
|
|||
|
||||
---
|
||||
|
||||
**OPS88 — verify do CI (PR e deploy): um Postgres com reset determinístico, um build, migrate/seed sem repetição (2026-08-24):** o `checks` do PR e o `verify` do deploy reconstruíam o mesmo estado até 4× por run (dois containers Postgres `postgres-int`/`postgres-build`, dois migrates explícitos, dois seeds, dois builds — cada build re-rodando migrate embutido; o build default `.next` nem era servido por nada). Agora: **um Postgres por run** com reset determinístico entre fases — script novo `pnpm db:reset` (`scripts/db-reset.mjs`: `DROP SCHEMA public CASCADE` → `CREATE SCHEMA public` → `pnpm migrate` → `pnpm db:seed:minimal`, guard fail-closed por construção: só host local E nome `teqo(_<wt>)?_test`, sem escape `ALLOW_REMOTE_DB` — um DROP não pode herdar o override remoto dos seeds) — e **um build por run** quando build+e2e rodam juntos (o e2e continua servindo o `.next-e2e`; o build default só roda quando é o único build da run; no `verify` do deploy ele saiu de vez, pois o e2e full sempre roda lá). Contrato do nome `_test` extraído para `TEST_DATABASE_NAME_RE` em `scripts/lib/cli.mjs` (fonte única com `assertTestDatabase.ts`); `scripts/db-reset.mjs` entra no `HIGH_RISK_EXACT` **e** no `BUILD_SURFACE_EXACT` (um PR só no reset arma o próprio step no CI), `scripts/lib/cli.mjs` entra no `HIGH_RISK_EXACT`; o mirror local `gate:ci` ganha o reset antes do build (paridade CI↔local, mesma condição do YAML); `deploy` no homeserver intocado (migrate-before-build OPS66 preservado — o migrate embutido do build vira no-op aceito). Docs canônicos (`TESTING.md`, `AGENT-OPS.md`) atualizados para o setup único. Contabilização honesta: ciclos de estado 4→2, Postgres 2→1, builds 2→1, migrates executados 4→3 (residual = no-op embutido), seeds 2→2. Verificação: guards fail-closed (3 recusas), reset sobre resíduo real (tabela junk somada → baseline 435 municípios + seed minimal de volta), idempotência (re-execução), suite int **full** verde pós-reset (830 tests); `pnpm lint`/`tsc`/`format:check`/`knip`/`check:cycles`/`test:unit` (2445) verdes.
|
||||
|
||||
**OPS88 — verify do CI (PR e deploy): um Postgres com reset determinístico, um build, migrate/seed sem repetição (2026-08-24):** o `checks` do PR e o `verify` do deploy reconstruíam o mesmo estado até 4× por run (dois containers Postgres `postgres-int`/`postgres-build`, dois migrates explícitos, dois seeds, dois builds — cada build re-rodando migrate embutido; o build default `.next` nem era servido por nada). Agora: **um Postgres por run** com reset determinístico entre fases — script novo `pnpm db:reset` (`scripts/db-reset.mjs`: `DROP SCHEMA public CASCADE` → `CREATE SCHEMA public` → `pnpm migrate` → `pnpm db:seed:minimal`, guard fail-closed por construção: só host local E nome `teqo(_<wt>)?_test`, sem escape `ALLOW_REMOTE_DB` — um DROP não pode herdar o override remoto dos seeds) — e **um build por run** quando build+e2e rodam juntos (o e2e continua servindo o `.next-e2e`; o build default só roda quando é o único build da run; no `verify` do deploy ele saiu de vez, pois o e2e full sempre roda lá). Contrato do nome `_test` extraído para `TEST_DATABASE_NAME_RE` em `scripts/lib/cli.mjs` (fonte única com `assertTestDatabase.ts`); `scripts/db-reset.mjs` entra no `HIGH_RISK_EXACT` (harness de teste) e o mirror local `gate:ci` ganha o reset antes do build (paridade CI↔local); `deploy` no homeserver intocado (migrate-before-build OPS66 preservado — o migrate embutido do build vira no-op aceito). Contabilização honesta: ciclos de estado 4→2, Postgres 2→1, builds 2→1, migrates executados 4→3 (residual = no-op embutido), seeds 2→2. Verificação: guards fail-closed (3 recusas), reset sobre resíduo real (tabela junk somada → baseline 435 municípios + seed minimal de volta), idempotência (re-execução), suite int **full** verde pós-reset (830 tests); `pnpm lint`/`tsc`/`format:check`/`knip`/`check:cycles`/`test:unit` (2445) verdes.
|
||||
|
||||
**OPS83 — desbloquear deploy: estabilizar os e2e do verify sem reduzir cobertura; AgendaFeed volta a rodar em prod (2026-08-24):** o deploy manual (`deploy.yml`) estava bloqueado pelo job `verify` (run #15: 1 failed + 7 flaky + 2 skipped + 1 did-not-run). Causas estudadas e corrigidas por reescrita de specs (settle/poll/hermeticidade/escopo de locator) — **nenhum** `test.flaky`, nenhuma remoção de cobertura, nenhuma mudança de workers/retries, zero mudança de app/contrato/acesso: (1) kill switch da home — o poll de server-HTML convergia enquanto a seção renderizada ainda mostrava o link do Instagram; passou a pollar o **DOM rendered** escopado a `[data-home-section="contents"]` (`expectSectionCardCount`, usado também no sibling de fallback IG); (2) section-hidden — o revalidate `tag=posts` era engolido por `.catch`; agora o POST é **assertado** e a convergência ganha orçamento explícito (não bump cego); (3) SavedFilters strict-mode — o link 'Municípios' agora é escopado ao container `[data-slot="sidebar"]` (2º match = bottom nav, #669 absorvida); (4) People — gate `S:` + settle do stream antes de asserir rows (testes 15, pós-search e C118); (5) Municípios:642 — poll DOM da liderança específica em vez de `.first()` no stream; (6) Municípios:386 B176 — `settleMunicipalityStream` entre remove-chip e o próximo clique de opção; (7) Newsletter:222 — `toHaveURL(/#novidades/)` sem `$` (o `?e2e=scroll-…` é legítimo); (8) DemandVisibility:62 — `.goto` com cache-buster para a asserção de 404 do peer (a cache RSC servia a demanda acessível após a troca de cookie). **AgendaFeed (Decision B):** os 2 `test.skip(isProdMode)` foram **removidos** — o build e2e agora inlina `NEXT_PUBLIC_SITE_URL=https://feed.e2e.teqo.test` (`.test` TLD, passa o fail-closed `requireProductionDNSOrigin`), o diálogo gera o link canônico e os testes asserem o **path** e buscam via `campaign.baseURL + path`; confirmado que share usa `window.location.origin`, webauthn é desacoplado via request-host e webhooks usam `campaign.baseURL` — nenhum outro e2e depende do origin. Escopo config: `playwright.config.ts` (default do webServer env em prod), `deploy.yml` e `ci-pr.yml` (env do build-for-e2e + do passo de testes). Verificação: specs afetadas verdes em dev mode (2×) e AgendaFeed verde em `E2E_PROD=1`; `pnpm lint`/`tsc`/`format:check`/`knip`/`check:cycles`/`test:unit` (2443) verdes; PR em main com auto-merge.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Three layers, all local-database-only (see `local-database` skill; tests refuse
|
|||
|
||||
**Level preference (pyramid):** unit first (pure logic) → integration for Payload/DB boundaries → e2e only when the flow has real benefit the lower layers don't cover (critical user path, UX/navigation, URL contract). Canonical definition: `test-driven-development` skill.
|
||||
|
||||
CI (`ci-pr.yml`) is a **single sequential job `checks`** (OPS62 X1): guards → lint (zero warnings) → format → typecheck → knip → cycles → unit/int (**affected by the PR diff**: `vitest --changed` against the PR base via `scripts/test-affected.mjs`, full-suite fallback on high-risk paths) → build → e2e (manifest path→spec via `scripts/e2e-affected.mjs` + `scripts/lib/e2e-affected-manifest.mjs`, blocking; **only in mode `selected` — OPS72: the PR never runs full e2e**; production build + `list`/`github` reporters in CI) — against two isolated Postgres 17 services (int / build+e2e) with **`db:seed:minimal`**. Fail-fast is structural: a failed step stops the run; e2e runs with `PLAYWRIGHT_WORKERS=4`. There is no main verifier (`ci.yml` eliminated in OPS71): the full net including e2e runs in the deploy `verify` job before publishing (manual dispatch).
|
||||
CI (`ci-pr.yml`) is a **single sequential job `checks`** (OPS62 X1): guards → lint (zero warnings) → format → typecheck → knip → cycles → unit/int (**affected by the PR diff**: `vitest --changed` against the PR base via `scripts/test-affected.mjs`, full-suite fallback on high-risk paths) → build → e2e (manifest path→spec via `scripts/e2e-affected.mjs` + `scripts/lib/e2e-affected-manifest.mjs`, blocking; **only in mode `selected` — OPS72: the PR never runs full e2e**; production build + `list`/`github` reporters in CI) — against ONE Postgres 17 service, with a deterministic **`pnpm db:reset`** (drop schema → migrate → seed:minimal) between the int phase and the build/e2e phase (OPS88). Fail-fast is structural: a failed step stops the run; e2e runs with `PLAYWRIGHT_WORKERS=4`. There is no main verifier (`ci.yml` eliminated in OPS71): the full net including e2e runs in the deploy `verify` job before publishing (manual dispatch).
|
||||
|
||||
**Local e2e (OPS72 — skill step, discretionary):** before push, run the e2e specs you created + the same affected surface — `pnpm test:e2e:affected` runs `e2e-affected.mjs` against `origin/main`, migrates/seeds, then `pnpm test:e2e` with the selected specs (full suite on high-risk diffs). High-risk diffs diverge from PR CI by design: the PR runs no e2e (selected only, never full) and the local mirror runs the full suite. Forward extra Playwright args after `--` for a narrowed repro. Opt into production mode with `E2E_PROD=1 pnpm test:e2e:affected` (CI and prod mode serve the production build via `pnpm start`; plain local runs use `pnpm dev` — dev-mode flakes like cold-compile timeouts and dev-only console warnings do not reproduce in prod mode).
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
**OPS88 — verify do CI (PR e deploy): um Postgres com reset determinístico, um build, migrate/seed sem repetição (2026-08-24):** o `checks` do PR e o `verify` do deploy reconstruíam o mesmo estado até 4× por run (dois containers Postgres `postgres-int`/`postgres-build`, dois migrates explícitos, dois seeds, dois builds — cada build re-rodando migrate embutido; o build default `.next` nem era servido por nada). Agora: **um Postgres por run** com reset determinístico entre fases — script novo `pnpm db:reset` (`scripts/db-reset.mjs`: `DROP SCHEMA public CASCADE` → `CREATE SCHEMA public` → `pnpm migrate` → `pnpm db:seed:minimal`, guard fail-closed por construção: só host local E nome `teqo(_<wt>)?_test`, sem escape `ALLOW_REMOTE_DB` — um DROP não pode herdar o override remoto dos seeds) — e **um build por run** quando build+e2e rodam juntos (o e2e continua servindo o `.next-e2e`; o build default só roda quando é o único build da run; no `verify` do deploy ele saiu de vez, pois o e2e full sempre roda lá). Contrato do nome `_test` extraído para `TEST_DATABASE_NAME_RE` em `scripts/lib/cli.mjs` (fonte única com `assertTestDatabase.ts`); `scripts/db-reset.mjs` entra no `HIGH_RISK_EXACT` (harness de teste) e o mirror local `gate:ci` ganha o reset antes do build (paridade CI↔local); `deploy` no homeserver intocado (migrate-before-build OPS66 preservado — o migrate embutido do build vira no-op aceito). Contabilização honesta: ciclos de estado 4→2, Postgres 2→1, builds 2→1, migrates executados 4→3 (residual = no-op embutido), seeds 2→2. Verificação: guards fail-closed (3 recusas), reset sobre resíduo real (tabela junk somada → baseline 435 municípios + seed minimal de volta), idempotência (re-execução), suite int **full** verde pós-reset (830 tests); `pnpm lint`/`tsc`/`format:check`/`knip`/`check:cycles`/`test:unit` (2445) verdes.
|
||||
**OPS88 — verify do CI (PR e deploy): um Postgres com reset determinístico, um build, migrate/seed sem repetição (2026-08-24):** o `checks` do PR e o `verify` do deploy reconstruíam o mesmo estado até 4× por run (dois containers Postgres `postgres-int`/`postgres-build`, dois migrates explícitos, dois seeds, dois builds — cada build re-rodando migrate embutido; o build default `.next` nem era servido por nada). Agora: **um Postgres por run** com reset determinístico entre fases — script novo `pnpm db:reset` (`scripts/db-reset.mjs`: `DROP SCHEMA public CASCADE` → `CREATE SCHEMA public` → `pnpm migrate` → `pnpm db:seed:minimal`, guard fail-closed por construção: só host local E nome `teqo(_<wt>)?_test`, sem escape `ALLOW_REMOTE_DB` — um DROP não pode herdar o override remoto dos seeds) — e **um build por run** quando build+e2e rodam juntos (o e2e continua servindo o `.next-e2e`; o build default só roda quando é o único build da run; no `verify` do deploy ele saiu de vez, pois o e2e full sempre roda lá). Contrato do nome `_test` extraído para `TEST_DATABASE_NAME_RE` em `scripts/lib/cli.mjs` (fonte única com `assertTestDatabase.ts`); `scripts/db-reset.mjs` entra no `HIGH_RISK_EXACT` **e** no `BUILD_SURFACE_EXACT` (um PR só no reset arma o próprio step no CI), `scripts/lib/cli.mjs` entra no `HIGH_RISK_EXACT`; o mirror local `gate:ci` ganha o reset antes do build (paridade CI↔local, mesma condição do YAML); `deploy` no homeserver intocado (migrate-before-build OPS66 preservado — o migrate embutido do build vira no-op aceito). Docs canônicos (`TESTING.md`, `AGENT-OPS.md`) atualizados para o setup único. Contabilização honesta: ciclos de estado 4→2, Postgres 2→1, builds 2→1, migrates executados 4→3 (residual = no-op embutido), seeds 2→2. Verificação: guards fail-closed (3 recusas), reset sobre resíduo real (tabela junk somada → baseline 435 municípios + seed minimal de volta), idempotência (re-execução), suite int **full** verde pós-reset (830 tests); `pnpm lint`/`tsc`/`format:check`/`knip`/`check:cycles`/`test:unit` (2445) verdes.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Impl: Verify do CI (PR e deploy): um Postgres com reset determinístico, um build, migrate/seed sem repetição
|
||||
|
||||
Status: rascunho
|
||||
Status: aprovado (gate humano 2026-08-24)
|
||||
Atualizado em: 2026-08-24
|
||||
Issue: #834
|
||||
Intenção: docs/plans/ops88-ci-verify-setup-unico.md
|
||||
|
|
@ -82,7 +82,7 @@ flowchart LR
|
|||
- **`.github/workflows/ci-pr.yml`**: remove `postgres-build` + steps 5433; adiciona step "Reset database (single service)" com o `if:` de D2; ajusta `if:` do build default; atualiza comentário de serviços.
|
||||
- **`.github/workflows/deploy.yml`** (job `verify`): espelho do acima, sem skips — remove `postgres-build`, remove build default, adiciona reset entre int e build `.next-e2e`.
|
||||
- **`scripts/gate-ci.mjs`**: `pnpm db:reset` antes do build quando há fase de build — paridade local com o CI novo (o mirror deixa de construir sobre resíduo do int).
|
||||
- **`scripts/lib/test-affected-core.mjs`**: `HIGH_RISK_EXACT` += `'scripts/db-reset.mjs'` — mudança no reset é harness de teste, deve rodar suíte cheia.
|
||||
- **`scripts/lib/test-affected-core.mjs`**: `HIGH_RISK_EXACT` += `'scripts/db-reset.mjs'` **e** `'scripts/lib/cli.mjs'` (mudança no reset ou no contrato `_test` é harness, deve rodar suíte cheia); `BUILD_SURFACE_EXACT` += `'scripts/db-reset.mjs'` — sem isso um PR só no reset classificaria `build_mode='none'` e o próprio step Reset pularia no CI (achado do simplify).
|
||||
- **Docs:** entrada em `docs/changelog/<data>-ops88.md` + `pnpm changelog:build`; menção curta no AGENTS.md (bloco de CI) se o runbook do job mudar — o runbook de deploy (`docs/ops/teqo-1313-deploy.md`) **não** muda.
|
||||
- **Migration:** sem migration (nenhum schema/dado novo).
|
||||
- **Access / Consent:** nenhum (nenhuma chave de Consent tocada; fail-closed intacto).
|
||||
|
|
|
|||
|
|
@ -52,7 +52,13 @@ if (!LOCAL_HOSTS.has(parsedUrl.hostname)) {
|
|||
)
|
||||
}
|
||||
|
||||
const databaseName = decodeURIComponent(parsedUrl.pathname.replace(/^\//, ''))
|
||||
let databaseName
|
||||
try {
|
||||
databaseName = decodeURIComponent(parsedUrl.pathname.replace(/^\//, ''))
|
||||
} catch {
|
||||
die('DATABASE_URL database name has invalid percent-encoding.')
|
||||
}
|
||||
|
||||
if (!TEST_DATABASE_NAME_RE.test(databaseName)) {
|
||||
die(
|
||||
`Refusing to reset database "${databaseName}" — the name must match ` +
|
||||
|
|
@ -60,18 +66,22 @@ if (!TEST_DATABASE_NAME_RE.test(databaseName)) {
|
|||
)
|
||||
}
|
||||
|
||||
const pool = new pg.Pool({ connectionString: databaseUrl, max: 1 })
|
||||
try {
|
||||
console.log(`[db:reset] Dropping schema public on "${databaseName}"…`)
|
||||
await pool.query('DROP SCHEMA public CASCADE')
|
||||
await pool.query('CREATE SCHEMA public')
|
||||
console.log('[db:reset] Schema rebuilt — running migrate…')
|
||||
} finally {
|
||||
await pool.end()
|
||||
const pool = new pg.Pool({ connectionString: databaseUrl, max: 1 })
|
||||
try {
|
||||
console.log(`[db:reset] Dropping schema public on "${databaseName}"…`)
|
||||
await pool.query('DROP SCHEMA public CASCADE')
|
||||
await pool.query('CREATE SCHEMA public')
|
||||
console.log('[db:reset] Schema rebuilt — running migrate…')
|
||||
} finally {
|
||||
await pool.end()
|
||||
}
|
||||
|
||||
execFileSync('pnpm', ['migrate'], { stdio: 'inherit' })
|
||||
console.log('[db:reset] Migrations applied — seeding minimal baseline…')
|
||||
execFileSync('pnpm', ['db:seed:minimal'], { stdio: 'inherit' })
|
||||
} catch (error) {
|
||||
die(`reset failed: ${error?.message ?? error}`)
|
||||
}
|
||||
|
||||
execFileSync('pnpm', ['migrate'], { stdio: 'inherit' })
|
||||
console.log('[db:reset] Migrations applied — seeding minimal baseline…')
|
||||
execFileSync('pnpm', ['db:seed:minimal'], { stdio: 'inherit' })
|
||||
|
||||
console.log('[db:reset] ✓ deterministic baseline ready (migrate + db:seed:minimal).')
|
||||
|
|
|
|||
|
|
@ -125,7 +125,8 @@ const main = async () => {
|
|||
}
|
||||
|
||||
// --- Phase 2 (expensive) — only when phase 1 would have been green ---
|
||||
const needsDb = scope.test.mode !== 'none' || scope.build.mode !== 'none'
|
||||
const needsDb =
|
||||
scope.test.mode !== 'none' || scope.build.mode !== 'none' || scope.e2e.mode === 'selected'
|
||||
if (needsDb) {
|
||||
console.log('\n[gate:ci] ▶ preflight (teqo_test)')
|
||||
const dbOk = await diagnoseDatabaseTarget({
|
||||
|
|
@ -156,13 +157,15 @@ const main = async () => {
|
|||
console.log('\n[gate:ci] ⊘ test:int skipped (no src/tests blast radius)')
|
||||
}
|
||||
|
||||
// OPS88: mirror of the CI single-Postgres reset — the build phase starts
|
||||
// from the same clean baseline (drop schema → migrate → seed:minimal) the
|
||||
// e2e build would get in CI, instead of int fixture residue.
|
||||
if (scope.build.mode === 'none' && scope.e2e.mode !== 'selected') {
|
||||
console.log('\n[gate:ci] ⊘ db:reset skipped (no build/e2e phase)')
|
||||
} else {
|
||||
// OPS88: mirror of the CI single-Postgres reset — the build phase below
|
||||
// starts from the same clean baseline (drop schema → migrate → seed:minimal)
|
||||
// the e2e build gets in CI, instead of int fixture residue. Same condition
|
||||
// as the CI step. (The gate still builds the default `.next`, not
|
||||
// `.next-e2e` — e2e never runs here, OPS72.)
|
||||
if (scope.build.mode !== 'none' || scope.e2e.mode === 'selected') {
|
||||
run('db:reset', 'pnpm', ['db:reset'], dbEnv)
|
||||
} else {
|
||||
console.log('\n[gate:ci] ⊘ db:reset skipped (no build/e2e phase)')
|
||||
}
|
||||
|
||||
if (scope.build.mode === 'none') {
|
||||
|
|
|
|||
|
|
@ -31,10 +31,12 @@ export const loadCliEnv = () => {
|
|||
|
||||
/**
|
||||
* Local database hosts — docker-compose service name, the job-network service
|
||||
* names of the Forgejo-era runner (OPS62 X1: services by name, no host publish —
|
||||
* the GitHub hosted CI reaches services on published localhost ports, OPS71),
|
||||
* and the bridge gateway (OPS50: legacy per-port publish kept in the
|
||||
* allowlist). THE one list.
|
||||
* names of the Forgejo-era runner (OPS62 X1: services by name, no host
|
||||
* publish — the GitHub hosted CI reaches services on published localhost
|
||||
* ports, OPS71; `postgres-int` remains active, `postgres-build` is a
|
||||
* defensive legacy entry since OPS88 removed the second container), and the
|
||||
* bridge gateway (OPS50: legacy per-port publish kept in the allowlist).
|
||||
* THE one list.
|
||||
*/
|
||||
export const LOCAL_HOSTS = new Set([
|
||||
'localhost',
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ export const HIGH_RISK_EXACT = new Set([
|
|||
'scripts/vitest-changed-or-full.mjs',
|
||||
'scripts/ci-scope.mjs',
|
||||
'scripts/db-reset.mjs',
|
||||
'scripts/lib/cli.mjs',
|
||||
'scripts/check-test-locations.mjs',
|
||||
'scripts/check-plans-only-pr-closes.mjs',
|
||||
'scripts/lib/plansOnlyClosesGuard.mjs',
|
||||
|
|
@ -90,6 +91,9 @@ const BUILD_SURFACE_EXACT = new Set([
|
|||
'src/payload.config.ts',
|
||||
'src/payload-types.ts',
|
||||
'tsconfig.json',
|
||||
// OPS88: a change to the reset script must exercise it — build_mode='build'
|
||||
// arms the Reset step in ci-pr.yml (and the mirror in gate-ci.mjs).
|
||||
'scripts/db-reset.mjs',
|
||||
])
|
||||
|
||||
const BUILD_SURFACE_PREFIXES = [
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@
|
|||
* looks test-like.
|
||||
*/
|
||||
|
||||
// Self-hosted Forgejo runner: services are reached by NAME on the job network
|
||||
// (postgres-int / postgres-build, OPS62 X1) or, on the legacy per-port
|
||||
// publish, through the bridge gateway IP (RFC1918). Same source as the CLI
|
||||
// guard so both guards admit exactly the same hosts (OPS50/OPS62).
|
||||
// `postgres-int`/`postgres-build` are legacy Forgejo job-network service
|
||||
// names (OPS62 X1), kept in the allowlist defensively — since OPS88 only one
|
||||
// service (`postgres-int`) exists, reached at localhost:5432 on hosted
|
||||
// runners. Same source as the CLI guard so both guards admit exactly the
|
||||
// same hosts (OPS50/OPS62).
|
||||
// TEST_DATABASE_NAME_RE is the shared name contract (cli.mjs) — the same
|
||||
// regex `scripts/db-reset.mjs` uses before dropping the schema (OPS88).
|
||||
import { defaultGatewayHost, TEST_DATABASE_NAME_RE } from '../../scripts/lib/cli.mjs'
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ describe('LOCAL_HOSTS (OPS50/OPS62 — self-hosted runner services by name, lega
|
|||
}
|
||||
})
|
||||
|
||||
it('admits the job-network service names (OPS62 X1: no host publish)', () => {
|
||||
it('keeps the legacy job-network service names in the allowlist (OPS62 X1; postgres-build dormant since OPS88)', () => {
|
||||
expect(LOCAL_HOSTS.has('postgres-int')).toBe(true)
|
||||
expect(LOCAL_HOSTS.has('postgres-build')).toBe(true)
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue