Merge pull request #568 from fsolla/OPS23-ops18-action-de-promote-related-n-no-merge-falha-sempr

fix(OPS23): plan-issue-ready — disable package-manager-cache no setup-node
This commit is contained in:
Francisco Solla 2026-08-10 08:56:57 -03:00 committed by GitHub
commit a970f05c8a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 77 additions and 0 deletions

View file

@ -33,8 +33,12 @@ jobs:
PR_BODY: ${{ github.event.pull_request.body }}
steps:
- uses: actions/checkout@v5
# Script is plain Node (gh CLI, stdlib) — no pnpm. Disable
# package-manager-cache: setup-node@v5 defaults to true and looks for
# pnpm (packageManager in package.json) before pnpm/action-setup.
- uses: actions/setup-node@v5
with:
node-version: 24
package-manager-cache: false
- name: Promote Related #N awaiting plan → ready
run: node scripts/agent-promote-related-on-merge.mjs --pr "$PR_NUMBER"

View file

@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
---
**Recently resolved (2026-08-10):** **OPS23** (Issue #566) — a Action OPS18 (`plan-issue-ready-on-main-merge.yml`) falhava **sempre** no primeiro disparo real (PR #563, run 31381977772): o job roda `actions/setup-node@v5` sem `pnpm/action-setup` e o script é plain Node — com `package-manager-cache: true` (default do v5), o setup-node resolve o `packageManager` do package.json (`pnpm@10.11.0`) e procura o executável pnpm → `Unable to locate executable file: pnpm`, promote nunca rodava. Correção: `package-manager-cache: false` no setup-node (precedente exato dos jobs plain-Node: cooldown do `ci.yml`, `vercel-promote.yml`, `archive-cursor-agent.yml`; `pnpm/action-setup@v6` rejeitado — só existe onde há `pnpm install`). Varredura dos 21 usos de setup-node: `plan-issue-ready` era o único com o bug latente. Validação funcional = próximo merge de PR com `Related #N` (trigger `closed+merged→main` só dispara em merge real). Impl [`ops23-action-promote-related-setup-node-impl.md`](plans/ops23-action-promote-related-setup-node-impl.md).
**Recently resolved (2026-08-10):** **B189** (Issue #526) — o Sollinha ganhou a tool **`getMunicipalitiesWithoutUpdate`** (`src/utilities/ai/tools/getMunicipalitiesWithoutUpdate.ts`): "quais municípios estão sem atualização há mais de X dias" (cobertura do acompanhamento) no escopo do usuário, com **contagem total exaustiva** (sem top N — cobertura é "tudo além do limiar"), **limiar declarado na resposta** (`limiarDias` + `criterio` interpolado; default 30, ajustável por pergunta via `days` 1–365, estritamente maior), lista ordenada do mais velho ao mais recente com "nunca atualizado" no topo (estagnação máxima, sempre incluídos), itens com `diasSemAtualizacao`/`cidade`/`regiao`/`tipo` (ZE vs município inteiro)/**assessores** (nomes em query própria `campaignUser` — `name` não é identity-gated; nunca `depth`). **Fonte de recência = `municipality.lastUpdateAt`** (campo derivado pelos hooks de `municipalityUpdate`, admin read-only — a semântica compartilhada da família B185 "última atualização de campo", nunca `updatedAt` do município; sanity local provou create→coluna→delete→null via hooks); filtro de recência em JS com `municipalitySignalAgeInDays` (helper E9 reusado — coerência rótulo↔limiar sem boundary de instante no `where`). **Gate staff inline** (leader → `{ error: 'Leitura de municípios negada.' }` antes de qualquer query, shape da B180); leituras `overrideAccess: false, user: ctx.user` (assessor = portfólio automático); **guard fail-closed de escopo vazio** (assessor fora do portfólio → shape vazio escopado sem leitura). **Resolver de escopo extraído** da B185 para `src/utilities/ai/tools/aiToolScope.ts` (`resolveAIToolScope` — Salvador primeiro, município por alias, território acento-tolerante; 2º call site da família; nomes de tipos alinhados à extração em voo da B186 para o merge dela vir trivial; contrato da B185 inalterado, spec existente cobre a regressão) e **loader de nomes compartilhado** `aiToolQueries.ts` (fundiu `loadNamesByIds` da B185 com o lookup local — don't twin). Seção nova no system prompt: declarar sempre limiar+critério, resumir Salvador ("7 das 19 zonas sem atualização há mais de 30 dias") e agrupar por assessor quando pedido (mesmo precedente "o modelo agrupa pela lista" da B185). Unit tests novos (`municipalitiesWithoutUpdateTool.unit.spec.ts`, 15): gate fail-closed com payload intocado, critério + borda estrita (31→incluído/30→excluído/`days:15`), ordem (nunca por nome, depois mais velho→recente), escopos acento/Salvador/município/erro, ids do escopo no where, escopo vazio fail-closed, nomes de assessores, tipo ZE. Sem migration/access/Consent/UI. Intenção [`sollinha-municipios-sem-atualizacao-recente.md`](plans/sollinha-municipios-sem-atualizacao-recente.md); impl [`sollinha-municipios-sem-atualizacao-recente-impl.md`](plans/sollinha-municipios-sem-atualizacao-recente-impl.md).
**Recently resolved (2026-08-10):** **OPS21** (Issue #538) — `REVALIDATE_SECRET` adicionada na **Vercel production** (projeto `solla/jorgesolla`): diagnóstico D6 confirmado (env ausente em `vercel env ls production`; `POST /api/revalidate` respondia `500` "not configured" em prod, quebrando o runbook pós-seed/mudança direta de DB). Segredo gerado com `openssl rand -hex 32` (valor via stdin, nunca argv), `vercel env add REVALIDATE_SECRET production`; **fallback necessário:** o deploy existente congelava as envs em runtime → `vercel redeploy` do último deploy de produção (2×, pois o primeiro `env add` via pipe herdou o `\n` do `openssl` e gravou valor de 66 chars — removido e re-adicionado limpo). Validação real em prod: sem secret → `401`; secret errado → `401`; secret certo → `200 { revalidated: true, tag: 'posts' }`; tag desconhecida → `400`. Preview não adicionada (projeto sem Git conectado — `vercel env add ... preview` exige branch, precedente D6). Sem código/migration/access/UI. Impl [`revalidate-secret-vercel-production-impl.md`](plans/revalidate-secret-vercel-production-impl.md).

View file

@ -0,0 +1,71 @@
# Impl: OPS23 — Action de promote Related #N falha no merge (setup-node procura pnpm sem action-setup)
Status: aprovado
Atualizado em: 2026-08-10
Issue: #566
Intenção: body da Issue #566 (OPS23; chore sem plano de intenção próprio — o diagnóstico é a intenção)
Appetite restante: < 1h (1 linha de YAML + docs)
## Leitura da intenção
- **Outcome:** no próximo merge de PR com `Related #N`, o job `plan-issue-ready` roda de verdade e promove (ou soft-skipa) — nunca mais falha com `Unable to locate executable file: pnpm`.
- **O que NÃO negociar:** comportamento do job (promote idempotente OPS18) intacto; sem pnpm desnecessário; precedente de ci.yml respeitado.
- **O que reavaliar:** “adicionar `pnpm/action-setup@v6`” (hipótese da Issue) vs `package-manager-cache: false` — o precedente exato para job plain-Node já existe em 3 lugares.
## Abordagem recomendada
```mermaid
flowchart LR
Merge["PR merge → main"] --> Gate["if: Related # (existente)"]
Gate --> Fix["setup-node@v5 + package-manager-cache: false"]
Fix --> Script["node scripts/agent-promote-related-on-merge.mjs"]
```
**Opções consideradas:**
- A) `with: package-manager-cache: false` no setup-node@v5 do job
- B) adicionar `pnpm/action-setup@v6` antes do setup-node
- C) trocar o runner para imagem com node pré-instalado (sem setup-node)
**Recomendação:** A — porque:
1. O job **não usa pnpm** (script plain Node: stdlib + `gh` CLI; sem `pnpm install` no workflow). Instalar pnpm é custo (~10–20s/run) sem ganho.
2. Precedente **exato** no repo para “script plain Node, sem pnpm”: `vercel-promote.yml:31-34`, `archive-cursor-agent.yml:19-22` e o job cooldown do `ci.yml:196-202` (que tem o mesmo comentário citado na Issue). O B (action-setup) é o precedente dos jobs que **rodam** pnpm.
3. setup-node@v5 com `package-manager-cache: true` (default) resolve o `packageManager` do package.json (`pnpm@10.11.0`) e procura o executável pnpm — com o cache desligado, não há busca.
**Rejeitadas:** B — adiciona pnpm a um job que nunca o chama (diverge do padrão do repo: action-setup só onde há `pnpm install`); C — quebra o padrão ubuntu-latest + setup-node de todos os workflows, sem benefício.
### Componentes / mudanças
- **`.github/workflows/plan-issue-ready-on-main-merge.yml`:** adicionar `package-manager-cache: false` ao `with:` do setup-node@v5 (mesmo shape de `vercel-promote.yml`/`archive-cursor-agent.yml`). Nada mais muda — gate `Related #`, env, script e permissões intactos.
- **`docs/CHANGELOG-AGENTS.md`:** entrada curta OPS23.
- **`docs/AGENT-OPS.md`:** sem mudança — a tabela/nota OPS18 já existe e não menciona o setup; verificar se a menção existe e fica correta.
- **Migration / Access / Consent / UI:** N/A.
## Fases verificáveis
1. **Fix YAML** — edição única no workflow + validação sintática local (parse YAML com `node -e` via pacote `yaml` se disponível, ou `python3 -c 'import yaml'`).
2. **Docs** — changelog.
3. **Gates** — `pnpm gate:fast` (sem DB necessário — chore de workflow; scripts não mudaram). Entrega com `pnpm push`.
**Validação funcional (aceite da Issue):** o trigger `pull_request closed + merged` só dispara em merges reais em `main` — não é exercitável no CI do PR aberto (e não deve ser: o próprio contrato OPS18 proíbe rodar antes do merge). A validação real é o **próximo merge de PR com `Related #N`** (ex.: este PR de fix, se documentar o relacionamento — o fix não contém `Related #`, então o primeiro disparo real provavelmente é o próximo PR de plano; runbook anotado na Issue).
## Rabbit holes / Não escopo (engenharia)
- Corrigir outros workflows: **varredura feita** — `ci.yml` (5/5 com action-setup antes; cooldown já com `package-manager-cache: false`), `ci-pr.yml` (10/10 com action-setup), `vercel-promote.yml` e `archive-cursor-agent.yml` já com `package-manager-cache: false`, `agent-pool.yml` com action-setup (usa pnpm). `plan-issue-ready` era o único com o bug latente.
- Adicionar actionlint/check de workflows ao CI — outra Issue se desejada; fora deste chore.
- Teste unitário do YAML — sem infra de teste de workflow no repo; validação = parse + revisão.
## Riscos e mitigação
- **YAML inválido → job quebra de outro jeito:** mitiga parse local antes do push + diff mínimo revisável (1 linha).
- **`package-manager-cache: false` não resolver:** contra-evidência — é o mesmo mecanismo usado no cooldown job do ci.yml que roda há dias sem falha; se aparecer novo sintoma, o fallback B (action-setup) é documentado nesta decisão.
- **Falso negativo na validação real:** o primeiro merge com `Related #` pode ser semanas depois — anotado na Issue como validação pendente (o próprio run 31381977772 provou que o job dispara; o fix é o pré-requisito que faltava).
## Aceite de engenharia
- [x] Aceite de produto da intenção ainda coberto (job roda e promove no próximo merge com `Related #N`)
- [x] Invariantes AGENTS/engineering-standards (sem schema/access/UI)
- [x] Testes de domínio: N/A — sem mudança de script; pin do parse/`canPromotePlanIssue` já existe no OPS18
Self-score decision-quality: 4/5