diff --git a/.agents/rules/agent-pr-workflow.mdc b/.agents/rules/agent-pr-workflow.mdc index a8e5494b..3932cdc0 100644 --- a/.agents/rules/agent-pr-workflow.mdc +++ b/.agents/rules/agent-pr-workflow.mdc @@ -61,7 +61,8 @@ Do not wait for human approval on normal deliveries. Auto-merge fires when **req Branch protection on `main` requires only: - `checks` (GitHub Actions rollup from `ci-pr.yml`) -- `migration-lock` + +(`migration-lock` removed 2026-08-12 — the CI validates migration chains via migrate + int on every PR/main.) **Vercel preview is NOT a merge gate.** Production deploy is Actions-gated (`ci.yml` → `vercel deploy --prod` after full suite green). Do not block on Vercel Git status. diff --git a/.agents/rules/engineering-standards.mdc b/.agents/rules/engineering-standards.mdc index 609d1f81..b050ee07 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`. `migration-lock` só existe no GitHub (conta PRs abertos em schema). 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`, 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`). 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/skills/agent-pool/SKILL.md b/.agents/skills/agent-pool/SKILL.md index 3dffd77a..616378e0 100644 --- a/.agents/skills/agent-pool/SKILL.md +++ b/.agents/skills/agent-pool/SKILL.md @@ -98,7 +98,7 @@ Issues nascidas de `plan-issue` com `--plan` **não** entram como `ready`: ficam - **Falha terminal** (run ERROR/CANCELLED/EXPIRED ou fim sem PR): tick comenta, move a Issue para `blocked` e arquiva o agente. **Triage humana**: ler o run em cursor.com/agents, decidir — re-`ready` manual se transitório (o circuit breaker recusa a 3ª tentativa automática), corrigir a spec se sistêmico. - **Worker travado**: archive em cursor.com/agents → o próximo tick reconcilia como falha documentada. - **Duplicata**: impossível em condições normais (alocador único + lock otimista + `agentId` idempotente); o tick cancela runs extras se alguém spawnar manualmente. -- **`needs:migration`/`serializes:[migrations]`**: o tick não spawna enquanto houver PR aberto tocando schema (`migration-lock`) — re-avalia a cada tick. +- **`needs:migration`/`serializes:[migrations]`**: o tick não spawna enquanto houver PR aberto tocando schema (predicado `countOpenSchemaPrs` em `agent-pool-github.mjs`) — re-avalia a cada tick. - **Audit solitário**: `pause` antes, `resume` depois (a skill `engineering-audit` lembra). ## Smoke remoto (aceite — rodar na primeira ativação) diff --git a/.agents/skills/work-issue/execution-pipeline.md b/.agents/skills/work-issue/execution-pipeline.md index fb745fbd..0c47707d 100644 --- a/.agents/skills/work-issue/execution-pipeline.md +++ b/.agents/skills/work-issue/execution-pipeline.md @@ -32,8 +32,7 @@ entrega. 2. **`pnpm push -u origin HEAD`** 3. PR **Ready** (nunca draft): `gh pr create --base main` com `Closes #` 4. `gh pr merge --auto --merge` imediatamente -5. `gh pr checks --watch --required` (`checks` + `migration-lock`; ignore - Vercel Git) +5. `gh pr checks --watch --required` (`checks`; ignore Vercel Git) 6. CI flipa `done`/`in-prod` no merge. Comente na Issue o desfecho em uma linha. ## Deltas por ator diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index 79bcb203..effbc695 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -2,8 +2,12 @@ # Phase 1 (cheap, no Postgres): lint, format, typecheck, knip, cycles, unit # Phase 2 (expensive, Postgres; needs phase 1 green/skipped): int, build, e2e # Job-level skips come from the `scope` classifier (scripts/ci-scope.mjs). A -# phase-1 failure skips phase 2 via `needs` + explicit if. `migration-lock` -# stays parallel to phase 1 (cheap, independent). +# phase-1 failure skips phase 2 via `needs` + explicit if. +# +# Schema migrations are NOT serialized across PRs (migration-lock removed +# 2026-08-12): a stale chain is caught by `pnpm migrate` + `test:int` on every +# PR and on main (ci.yml), incl. campaignMigrationReconciliation — the check's +# hard fail deadlocked every open schema PR the moment two coexisted. # # High-risk diffs (schema, lockfile, classifiers — see # scripts/lib/test-affected-core.mjs) force full unit/int/e2e. Push to main @@ -365,28 +369,6 @@ jobs: if: failure() uses: ./.github/actions/cancel-workflow-on-failure - migration-lock: - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - uses: actions/checkout@v5 - - name: Count open PRs touching schema files - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} - run: | - set -euo pipefail - count=$(gh pr list --state open --limit 100 --json number,files \ - --jq '[.[] | select(.files != null and ([.files[].path | test("^(src/migrations/|src/payload-types.ts$|payload-types.ts$)")] | any))] | length') - echo "Open schema-touching PRs: $count" - if [ "$count" -gt 1 ]; then - echo "::error::More than one open PR touches src/migrations/ or payload-types.ts. Merge or close the other first." - exit 1 - fi - - name: Cancel workflow on failure - if: failure() - uses: ./.github/actions/cancel-workflow-on-failure - checks: name: checks runs-on: ubuntu-latest @@ -401,7 +383,6 @@ jobs: - int - build - e2e - - migration-lock - plans-only-closes if: always() steps: diff --git a/docs/AGENT-OPS.md b/docs/AGENT-OPS.md index 395c66c4..124984a5 100644 --- a/docs/AGENT-OPS.md +++ b/docs/AGENT-OPS.md @@ -30,7 +30,7 @@ main → ci.yml full suite → vercel deploy --prod (se verde) → requeue se HE ### Bypass de CI (cutover / transição) - **Local:** se `gate:push` ainda espelha pipeline antigo e bloqueia push legítimo da PR que o muda → `git push --no-verify` permitido como escape de transição (não WIP permanente). Documentado aqui; após o cutover, entregas passam no CI novo sem bypass. -- **Cutover humano:** pode relaxar temporary required checks se o flip de proteção travar merges; reverter para `checks` + `migration-lock` em `main` assim que o CI novo estiver verde. +- **Cutover humano:** pode relaxar temporary required checks se o flip de proteção travar merges; reverter para `checks` em `main` assim que o CI novo estiver verde. ## Comandos @@ -45,7 +45,7 @@ main → ci.yml full suite → vercel deploy --prod (se verde) → requeue se HE | `pnpm agent:file-miss -- --title ...` | Issue `kind:agent-miss` | | `pnpm agent:pool -- status\|tick --dry-run\|doctor` | Pool Cloud; start/stop via `gh workflow run agent-pool.yml` | | `pnpm push [-- -u origin HEAD …]` | Canônico: ensure-deps + gate:push + push | -| `pnpm configure:branch-protection [-- --dry-run]` | Proteção de `main`: `checks` + `migration-lock`, `strict=false`, 0 reviews | +| `pnpm configure:branch-protection [-- --dry-run]` | Proteção de `main`: `checks`, `strict=false`, 0 reviews | | `pnpm db:seed:minimal` | DB mínimo sintético | Labels: `ready|in-progress|blocked|done|in-prod`, `prio:*`, `kind:*`, `needs:*`. @@ -55,7 +55,7 @@ Labels: `ready|in-progress|blocked|done|in-prod`, `prio:*`, `kind:*`, `needs:*`. ## Contrato de PR 1. Schema → atualizar `db:seed:minimal` no mesmo PR (`needs:migration`). -2. ≤1 PR aberto tocando migrations / `payload-types.ts` (`migration-lock`). +2. Migrations NÃO são serializadas entre PRs: o CI (migrate + int, incl. `campaignMigrationReconciliation`) valida a cadeia em todo PR e na main; rebase antes de `migrate:create` continua obrigatório. 3. Ready + auto-merge: `gh pr create --base main` → `gh pr merge --auto --merge`; `gh pr checks --watch --required` (Vercel Git não é gate). ## CI por alvo diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md index 105876ad..a3b291bc 100644 --- a/docs/CHANGELOG-AGENTS.md +++ b/docs/CHANGELOG-AGENTS.md @@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente --- +**Recently resolved (2026-08-12):** **Remoção do `migration-lock`** (CI) — o check de `ci-pr.yml` que falhava quando >1 PR aberto tocava `src/migrations/`/`payload-types.ts` foi **removido** (deadlock de CI: com 2 PRs de schema abertos, AMBOS falhavam o check e nenhum mergeava sem bypass manual — 9 de 13 runs de CI falhos desde 11/08 foram por isso). A ameaça que ele serializava (cadeia de migrations com drift de snapshot) já é coberta pelo CI em todo PR e na main: `pnpm migrate` + `test:int` (incl. `campaignMigrationReconciliation.int.spec.ts`, checksum do schema migrado vs config); o conflito real (index.ts/payload-types.ts) resolve no rebase. Removido: job `migration-lock` do `ci-pr.yml` + `needs` do rollup `checks` + contexto required da branch protection (`pnpm configure:branch-protection` agora aplica só `checks`); pool mantém a serialização de spawn de issues `needs:migration` (`countOpenSchemaPrs` em `agent-pool-github.mjs` — independe do CI). Docs/skills atualizados (AGENT-OPS contrato de PR, GUARDRAILS, engineering-standards, agent-pool, work-issue, agent-pr-workflow). Sem migration/access/Consent/UI/schema. + **Recently resolved (2026-08-11):** **C115** (Issue #636) — **Google Calendar → Teqo**: quem tem permissão no calendário remarca/renomeia/cancela o compromisso direto no Google e a mudança volta para a atividade (bidirecional sobre o espelho C114). **Motor único bidirecional** no `runSyncPass` do C114 (estende o dono): para cada evento nosso — conteúdo difere? → **regra do relógio**: Google mais novo aplica **título + horário** (all-day incluído; prefixo `[Município] ` só é destripado quando bate com o município — desfazemos só a nossa prefixação; cap 160) com **registro auditável** no `updates` feed (`Google Calendar: …`, `author: null`, feed mostra "Google Calendar" como autor pelo prefixo) — senão o Teqo re-asserta. **Baseline do relógio = `activity.lastMirroredChangeAt`** (novo campo system-write, carimbado no beforeChange só quando um campo espelhado muda): task toggle/update/resultado bumpam `updatedAt` sem tocar o espelho — comparar contra `updatedAt` silenciaria uma edição pendente do Google. **Escrita reversa sem usuário** (`req.user` stripped): staff que dispara a passada não ganha autoria do registro nem passa pelo gate de re-agendamento do deputado. **Cancelamento:** `listEvents` com `showDeleted=true` — evento `status: 'cancelled'` (lixeira) com atividade `confirmado` → cancela **se o cancelamento for mais novo que o último carimbo** (senão é reopen do staff: o trashed é limpo e o evento recriado — nunca re-cancela); `realizado` é histórico protegido. **Remoção permanente** distinguida de "nunca criado" por snapshot `lastSeenEventIds` (JSONB `{calendarId, ids}`, gravado só em passada bem-sucedida e **excluindo ids que o próprio motor deletou** — senão o reopen virava re-cancelamento fantasma); troca de `calendarId` → snapshot stale → recria sem cancelar. **Webhook público** `POST /campanha/agenda/google-webhook/[secret]` (fora de `(app)`, precedente `ical/[secret]`, `maxDuration=60`, `force-dynamic`, allowlist do `campaignJsonMutationRoute` com razão): pre-filter barato (secret ≥32 + headers) ANTES do bootstrap do Payload + validação fail-closed em lib pura testada (`isGooglePushNotificationValid` — secret da URL + `X-Goog-Channel-ID` + `X-Goog-Resource-ID` + `X-Goog-Channel-Token`, constant-time, 404; `not_exists` → estado de erro; `sync` → ignora); ping sem payload → passada completa; 200 mesmo se a passada falhar (sem retry storm). **Canal lazy sem cron (Hobby):** `ensureGoogleCalendarPushChannel` em toda passada — watch com UUID novo, TTL 30d, **segredo rotaciona a cada criação** (URL vazada auto-cura na renovação), **novo canal persistido ANTES do stop do antigo** (falha do stop não orfana o novo); expira em <48h → watch com id novo + `channels.stop` do antigo; troca de calendário → re-watch; falha → `pushChannelError` (state), nunca pausa (o reverso roda em todo trigger local; canal só dá imediatismo — limitação de 30d de silêncio documentada). Campos novos na `googleCalendarSync` (system-write; **identity fields com `read: false`** — o conjunto de credenciais do webhook nunca sai por path de usuário; create admin-only + leitura prefere linha configurada); migration **hand-written** (mesmo motivo do C114). **Título editável pelo Google** exigiu `mutationKind: 'googleCalendarSync'` dedicado (título muda, slug canônico congelado) + **skip do hook de sync na própria escrita reversa** (sem passada aninhada). UI (Impeccable B): seção "Edições pelo Google" no dialog (copy + expiração do canal + erro). Testes: unit (reverse mapping 18, validação do webhook 7, dialog/feed render 6, allowlist), int (13 novos no motor com stub: Google novo aplica+converge, task toggle não silencia edição pendente, Teqo novo re-asserta, cancelled cancela+limpa, reopen pós-cancelamento não re-cancela, remoção permanente via snapshot, create falhou não cancela, troca de calendário não cancela+re-watch, all-day ida-e-volta, realizado protegido, description-only sem registro, ciclo do canal cria/renova/rotaciona/stop, falha de canal não pausa — asserções herméticas a atividades ambiente de arquivos irmãos no DB compartilhado), e2e (webhook 404 fail-closed + caminhos 200 validados: sync/not_exists/change). Suíte completa verde (unit 2064, int 749, e2e 5/5). Intenção [`c115-google-calendar-edicao-bidirecional.md`](plans/c115-google-calendar-edicao-bidirecional.md); impl [`c115-google-calendar-edicao-bidirecional-impl.md`](plans/c115-google-calendar-edicao-bidirecional-impl.md). **Recently resolved (2026-08-11):** **C129** (Issue #696) — dobradinha ganha **nome de legenda** (campo + display discreto sob o nome): campo `ballotName` (texto opcional, `maxLength: 30` — limite real do nome de urna do TSE; **sem index** — nunca é chave de busca/ordenação/facet, o nome real continua sendo a chave) em `stateDeputy`, ao lado de `party`. **Migration** `20260811_234822_add_state_deputy_ballot_name` (gerada; o diff do gerador re-emitia um no-op de `ADD VALUE 'lembranca'` por snapshot stale — limpa à mão para só o `ADD COLUMN`; nullable, sem backfill). **Display "discreto sob o nome"** (mesma família, `text-xs text-muted-foreground`, sem label — a posição é o rótulo) em 3 pontos: 2ª linha da célula de Nome de `/campanha/pessoas` via seam pura `peopleNameSubline` em `peopleData` (C130 estende com o fallback `?? city` — regra combinada final legenda-sobrepõe-base registrada nos dois planos; nesta branch, sem legenda nada muda na linha, aceite da intenção), sublinha fora da região click-to-edit da célula de Nome na lista de Dobradinhas (irmã da célula no flex-col — clicar nela nunca abre o editor do nome), e sublinha no Nome da seção Contato da ficha. **Edição** (onde já se edita dado de dobradinha, B163): coluna "Nome de legenda" na lista de Dobradinhas (mesma máquina de célula do partido, `editTrigger="cell"`, **default-hidden** em `DEFAULT_HIDDEN_COLUMN_IDS.dobradinhas` — precedente E-mail B197; a sublinha é o display always-on e a coluna o opt-in de edição) + campo no `StateDeputyForm` (criar e editar; schemas create/update/inline `trimmedOptionalText(30)`/`trimmedNullableText(30)` — vazio limpa o campo). Escrita: `updateStateDeputyBallotNameRecord` espelhando o partido (`runStaffEntityMutation`, staff-only, sem transação/locks/Consent novos — escrita single-collection); `CampaignInlineEditableField` ganhou `'ballotName'` (aditivo). Sem migration destrutiva/access/Consent. Testes: unit (merge carrega `ballotName`; `peopleNameSubline` legenda-only/null; default de colunas dobradinhas `['email','ballotName']`), int novo `campaignStateDeputyBallotName` (staff seta/limpa, advisor ok, leader negado, criação com legenda); verificado ao vivo em dev (pessoas/dobradinhas/ficha, edição inline persistindo na sublinha). Gates: unit 2062, int 750, build OK; knip = P3 ledgered pré-existente (idêntico no baseline). Intenção [`pessoas-nome-de-legenda-dobradinha.md`](plans/pessoas-nome-de-legenda-dobradinha.md); impl [`pessoas-nome-de-legenda-dobradinha-impl.md`](plans/pessoas-nome-de-legenda-dobradinha-impl.md). (feat(C129): nome de legenda da dobradinha — campo ballotName + display discreto sob o nome (Closes #696)) diff --git a/docs/CUTOVER-MAIN-ONLY.md b/docs/CUTOVER-MAIN-ONLY.md index 08a7f52a..b8e057d7 100644 --- a/docs/CUTOVER-MAIN-ONLY.md +++ b/docs/CUTOVER-MAIN-ONLY.md @@ -3,7 +3,7 @@ Completed 2026-08-01. Branch `stage` and GitHub Environment `stage` are deleted. Production deploys only from `ci.yml` after the full verifier is green. - [x] Code PR opened (main-only cutover) — merged to `main` -- [x] `pnpm configure:branch-protection` applied on `main` (required: `checks` + `migration-lock`, `strict=false`, 0 reviews) +- [x] `pnpm configure:branch-protection` applied on `main` (required: `checks` (+ `migration-lock` until 2026-08-12), `strict=false`, 0 reviews) - [x] No open PRs targeting `stage` at cutover time - [x] Repo secrets present (`VERCEL_*`, `POOL_GITHUB_TOKEN`) - [x] `VERCEL_ORG_ID` / `VERCEL_PROJECT_ID` synced from local `.vercel/project.json` (`vercel link`) diff --git a/docs/GUARDRAILS.md b/docs/GUARDRAILS.md index e415c2e7..6653d3e9 100644 --- a/docs/GUARDRAILS.md +++ b/docs/GUARDRAILS.md @@ -33,7 +33,6 @@ Ledger dos guardrails determinísticos colhidos de `kind:agent-miss` (fluxo `pnp | `src/lib` não importa `utilities/` (valores) | Pass 2 W2 | 2 | ESLint `no-restricted-imports` | | 0 ciclos de import | Pass 2 | 4 | madge (`pnpm check:cycles`) | | Dead code (arquivos/deps/exports em erro) | Pass 2 W4b | 4 | knip em CI | -| `migration-lock` (≤1 PR aberto tocando schema) | agentes paralelos | 4 | job em `ci-pr.yml` | | PR só `docs/plans/` sem `Closes`/`Fixes`/`Resolves` | [#116](https://github.com/fsolla/teqo/issues/116) OPS12 | 4 | `scripts/lib/plansOnlyClosesGuard.mjs` + `scripts/check-plans-only-pr-closes.mjs` + job `plans-only-closes` em `ci-pr.yml` | | Seed mínimo obrigatório com migration (contrato manifest) | OPS fluxo PR | 3 + 5 | `tests/unit/seedMinimalManifest.unit.spec.ts` + job CI migrate→seed→int | | Regex de template ancorada em e2e | P3-C | 3 | `codebaseConventions.unit.spec.ts` (ampliada para literais no Pass 4) | diff --git a/scripts/configure-branch-protection.mjs b/scripts/configure-branch-protection.mjs index b33d3128..e95b3019 100644 --- a/scripts/configure-branch-protection.mjs +++ b/scripts/configure-branch-protection.mjs @@ -4,8 +4,9 @@ * pnpm configure:branch-protection # apply * pnpm configure:branch-protection -- --dry-run * - * main: required checks `checks` + `migration-lock`, strict=false, 0 reviews. - * Feature PRs auto-merge when those contexts are green. + * main: required checks `checks`, strict=false, 0 reviews. + * Feature PRs auto-merge when that context is green. + * (migration-lock removed 2026-08-12 — see ci-pr.yml header.) */ import { execFileSync } from 'node:child_process' @@ -40,7 +41,7 @@ console.log(`[configure:branch-protection] repo ${repo}${dryRun ? ' (dry-run)' : ghApiJson('PUT', `repos/${repo}/branches/main/protection`, { required_status_checks: { strict: false, - contexts: ['checks', 'migration-lock'], + contexts: ['checks'], }, enforce_admins: false, required_pull_request_reviews: { @@ -51,6 +52,4 @@ ghApiJson('PUT', `repos/${repo}/branches/main/protection`, { allow_deletions: false, }) -console.log( - '[configure:branch-protection] done (main: checks + migration-lock, strict=false, 0 reviews).', -) +console.log('[configure:branch-protection] done (main: checks, strict=false, 0 reviews).') diff --git a/scripts/gate-ci.mjs b/scripts/gate-ci.mjs index 356d3cd5..2c8972a8 100644 --- a/scripts/gate-ci.mjs +++ b/scripts/gate-ci.mjs @@ -2,7 +2,7 @@ /** * 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). `migration-lock` is GitHub-only. + * (`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). diff --git a/scripts/lib/agent-pool-eligibility.mjs b/scripts/lib/agent-pool-eligibility.mjs index a3d97bb4..e8c0af10 100644 --- a/scripts/lib/agent-pool-eligibility.mjs +++ b/scripts/lib/agent-pool-eligibility.mjs @@ -5,9 +5,10 @@ * The pool consumes the SAME queue as `agent:claim` (buildClaimQueue) and then * applies stricter, fail-closed predicates: anything that needs a human * (`requirements-changed`, `needs:consent`, `blocked`) is out, migration-touching - * issues yield to the migration-lock while a schema PR is open, and an issue - * the pool already failed twice stays out (circuit breaker — enforced per - * candidate at claim time, where the comments are already being read). + * issues wait while a schema PR is open (pool-level serialization — the CI + * migration-lock was removed 2026-08-12), and an issue the pool already failed + * twice stays out (circuit breaker — enforced per candidate at claim time, + * where the comments are already being read). */ import { labelNames } from './agent-github.mjs' diff --git a/scripts/lib/agent-pool-github.mjs b/scripts/lib/agent-pool-github.mjs index 7c32a209..a760e100 100644 --- a/scripts/lib/agent-pool-github.mjs +++ b/scripts/lib/agent-pool-github.mjs @@ -72,8 +72,8 @@ export const prClosingIssue = (prs, issueNumber) => prs.find((pr) => new RegExp(`(?:closes|fixes) #${issueNumber}\\b`, 'i').test(pr.body ?? '')) ?? null -// Same path predicate as the ci-pr.yml migration-lock job (single source of -// truth for "schema-touching PR"). +// Path predicate for "schema-touching PR" — the pool serializes migration +// spawns while one is open (the CI migration-lock was removed 2026-08-12). const SCHEMA_PR_PATH = /^(src\/migrations\/|src\/payload-types\.ts$|payload-types\.ts$)/ export const countOpenSchemaPrs = () =>