From de70ae7e1eb101e24869c5daf225334e21d6fc22 Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Tue, 25 Aug 2026 10:15:22 -0300 Subject: [PATCH] docs(audit-pass6): -impl plans P6-1, P6-2, P6-M909, P6-M895 --- docs/plans/audit-pass6-bypass-ratchet-impl.md | 49 +++++++++++++++++ ...udit-pass6-m895-plan-data-literals-impl.md | 54 +++++++++++++++++++ docs/plans/audit-pass6-m909-disarm-impl.md | 51 ++++++++++++++++++ .../audit-pass6-perfil-page-gate-impl.md | 52 ++++++++++++++++++ 4 files changed, 206 insertions(+) create mode 100644 docs/plans/audit-pass6-bypass-ratchet-impl.md create mode 100644 docs/plans/audit-pass6-m895-plan-data-literals-impl.md create mode 100644 docs/plans/audit-pass6-m909-disarm-impl.md create mode 100644 docs/plans/audit-pass6-perfil-page-gate-impl.md diff --git a/docs/plans/audit-pass6-bypass-ratchet-impl.md b/docs/plans/audit-pass6-bypass-ratchet-impl.md new file mode 100644 index 00000000..a4f7cd0d --- /dev/null +++ b/docs/plans/audit-pass6-bypass-ratchet-impl.md @@ -0,0 +1,49 @@ +# Impl: Ratchet de bypass endurecido (P6-1) + +Status: aprovado +Atualizado em: 2026-08-25 +Issue: — +Intenção: docs/plans/entrega-engenharia-p6.md +Appetite restante: herdado (guarda classe 3, spec única — entrega S) + +## Leitura da intenção + +- **Outcome:** Comentário de módulo (header) não zera mais o arquivo — bypass só conta como documentado com comentário per-site; arquivos novos entram pinados na contagem atual; zero mudança de runtime. +- **O que NÃO negociar:** nenhuma mudança de comportamento, sem migration, sem dependências novas; o ratchet continua só-diminui (pins = contagem medida hoje, nunca arredondada para baixo). +- **O que reavaliar:** a premissa "arquivos novos invisíveis" — medida: a invisibilidade vem do header rule, não do mapa; o walk já cobre todo `src/` e `pinnedUndocumented.get(path) ?? 0` já faz arquivo sem pin com N indocumentados falhar. O bug é o header zerar a contagem. + +## Abordagem recomendada + +**Opções consideradas:** A | B | C +**Recomendação:** A — remover o zero-by-header em `countUndocumented` (mantendo a janela de 10 linhas per-site) e pinar os arquivos recém-visíveis na contagem medida. Delta mínimo que satisfaz (a)/(b)/(c). +**Rejeitadas:** B — apertar para "comentário contíguo imediatamente acima": rederrata ~64 arquivos, exige SUBIR pins de ~30 arquivos já pinados, violando o espírito só-diminui. C — migrar o pin map para snapshot JSON commitado: cerimônia nova sem ganho. + +### Componentes / mudanças + +- **`countUndocumented`** (`tests/unit/codebaseConventions.unit.spec.ts:691-704`): deletar o cálculo de `header` (linha 693) e a condição `!header.includes('bypass')` (linha 701). Documentado ⇔ `context` (10 linhas acima) contém "bypass". Atualizar a prosa do describe. +- **`pinnedUndocumented`** (648-689): adicionar pins P6-1 (comentário "P6-1 baseline"), valores = contagem per-site medida na fase 1 (o RED da fase 1 é a medição — colar os números que o RED reportar). Os pins existentes NÃO mudam se per-site == pin atual. +- **Migration:** sem migration. **Access/Consent:** nenhuma — guard-only. **UI:** N/A. + +## Fases verificáveis + +1. **Hardening do contador** — remover o header rule + atualizar a prosa; rodar o spec: `pnpm test:unit -- codebaseConventions` (bare). Esperado: RED nos arquivos recém-visíveis (`N undocumented (pin: 0)`). Negative check opcional: criar `src/utilities/__bypassProbe.ts` com 1 `overrideAccess: true` sem comentário → spec falha → deletar o probe. +2. **Pinning** — adicionar os pins medidos na fase 1; spec GREEN. +3. **Gates** — `pnpm gate:fast`; push via `pnpm push`. + +## Rabbit holes / Não escopo (engenharia) + +- **Comentários per-site nos piores arquivos** (`personDelete.ts`, `notification/*`, `Consent.ts`, `loadNamesByIds.ts`) — FORA de escopo deste pass; os pins os cobrem e o ratchet bloqueia crescimento. +- Apertar a janela para comentário contíguo (Opção B); re-justificar bypasses; mexer no `localApiOverrideAccessConventions.unit.spec.ts`. + +## Riscos e mitigação + +- Contagem medida pelo RED da fase 1 (não estimativa); nenhum pin abaixo do medido — fase 2 confere GREEN. +- O probe negativo é temporário e deletado na mesma fase. + +## Aceite de engenharia + +- [x] Aceite de produto da intenção ainda coberto (guard hardening, runtime intocado) +- [x] Invariantes AGENTS/engineering-standards (sem migration, sem deps, sem twin) +- [x] Testes de domínio: a spec É o teste — RED sem hardening (fase 1), GREEN com (fase 2) + +Self-score decision-quality: 5/5 diff --git a/docs/plans/audit-pass6-m895-plan-data-literals-impl.md b/docs/plans/audit-pass6-m895-plan-data-literals-impl.md new file mode 100644 index 00000000..987c9279 --- /dev/null +++ b/docs/plans/audit-pass6-m895-plan-data-literals-impl.md @@ -0,0 +1,54 @@ +# Impl: Guardrail P6-M895 — plano de intenção registra os dados literais da decisão + +Status: aprovado +Atualizado em: 2026-08-25 +Issue: #895 +Intenção: docs/plans/entrega-engenharia-p6.md +Appetite restante: herdado (~0,5 dia eng, docs-only) + +## Leitura da intenção + +- **Outcome:** todo plano de intenção que registra uma decisão de DADOS grava os valores literais (IDs, nomes de modelo, envs, tabelas de threshold) — nunca só narrativa. O GATE de planos ganha um prompt que cobra isso. +- **O que NÃO negociar:** classe **6 declarada judgment-only** — sem detector determinístico; nada de código/teste; não fingir que doc é guarda. +- **O que reavaliar:** pontos de aterrissagem exatos no skill (template vs SKILL.md). + +## Abordagem recomendada + +**Opções:** A (só template) | B (só checklist do gate) | C (ambos) +**Recomendação: C.** O miss tem dois lados: o escritor omitiu os IDs E o gate aprovou sem eles. +**Rejeitadas:** A (gate cego — repete a falha onde ela escapou); B (escritores sem instrução não produzem o dado). + +### Componentes / mudanças + +- **`.agents/skills/plan-issue/intention-template.md`**: novo bloco **"Dados da decisão (literais)"** — tabela/lista verbatim de valores a aplicar (flag→ID, lista de modelos, strings exatas, envs). Presente SEMPRE que a intenção fixa valores de dados; "N/A" explícito quando não houver. +- **`.agents/skills/plan-issue/SKILL.md`**: Checklist e Passo 4 (GATE) ganham o prompt "decisão de dados → valores literais presentes no plano?". +- **`docs/GUARDRAILS.md`**: linha na tabela Ativos — origem `#895`, classe `6 (judgment-only declarada)`, mecanismo `bloco "Dados da decisão (literais)" + prompt no gate`. +- **`docs/changelog/2026-08-25-audit-pass6-m895.md`**: entrada append-only. +- **Migration / Access / UI:** sem migration, sem access, sem UI. + +## Fases verificáveis + +1. **Template** — bloco + exemplo concreto do próprio miss (OPS95: tabela flag→ID do mapa). +2. **Gate** — prompt no Checklist/Passo 4 do SKILL.md. +3. **Ledger + changelog** — linha em GUARDRAILS.md; changelog append-only. +4. **Gates** — `pnpm gate:fast` + docs-guards; push via `pnpm push`. + +## Rabbit holes / Não escopo + +- Não inventar detector de "prosa sem dados" (infalsificável; viola classe 6). +- Não varrer `docs/plans/` retroativamente; não estender aos planos `-impl`; não reestruturar GUARDRAILS.md além da linha nova. + +## Riscos e mitigação + +- Convenção vira "bloco de cortesia": o prompt fica no GATE humano (Passo 4), não em automação; exemplo do miss nomeado. +- Docs-only: changelog append-only em arquivo novo; nunca editar HISTORY/agregado. + +## Aceite de engenharia + +- [x] `intention-template.md` com o bloco + exemplo #895 + regra N/A explícita +- [x] SKILL.md gate cobra os literais +- [x] GUARDRAILS.md linha classe 6 declarada, origem #895 +- [x] Changelog append-only; `pnpm gate:fast` verde +- [x] Zero código/teste/migration — delta de comportamento nulo + +Self-score decision-quality: 5.0 ≥ 4 diff --git a/docs/plans/audit-pass6-m909-disarm-impl.md b/docs/plans/audit-pass6-m909-disarm-impl.md new file mode 100644 index 00000000..786bd94f --- /dev/null +++ b/docs/plans/audit-pass6-m909-disarm-impl.md @@ -0,0 +1,51 @@ +# Impl: Guardrail P6-M909 — desarme determinístico do auto-merge da /testing-audit + +Status: aprovado +Atualizado em: 2026-08-25 +Issue: #909 +Intenção: docs/plans/entrega-engenharia-p6.md +Appetite restante: 0,5 d + +## Leitura da intenção + +- **Outcome:** o desarme do auto-merge deixa de depender do agente lembrar a ordem — `scripts/testing-audit-disarm.mjs` chamado no passo que cria o PR (cria → desarma → verifica `null`, atômico) + fiação na skill /testing-audit. +- **O que NÃO negociar:** o safety-net (`agent-pr-ready-automerge.yml` → `github-pr-automerge.mjs`) é intocável. PR ready (nunca draft) segue contrato. Guarda classe 3. +- **O que reavaliar:** a Fase 5 atual da skill usa `gh pr merge --disable-auto`/`gh pr view` — scripts do repo falam REST/GraphQL com `GITHUB_TOKEN` (`github-api.mjs`, `issue.mjs`). + +## Abordagem recomendada + +**Opções:** A (só `--pr N` desarmar) | B (fallback draft manual) | C (criar+desarmar+verificar + `--draft-on-failure`) +**Recomendação:** C — script faz criar+desarmar (`--head/--title/--body-file`) ou desarmar PR existente (`--pr N`, para rearms pós-`synchronize`); falha de verificação converte a draft via GraphQL e sai 1. O fallback também não pode depender do agente. +**Rejeitadas:** A (dois comandos ≠ atômico); B (a mesma ordem humana que falhou). + +### Componentes / mudanças + +- **`scripts/lib/github-api.mjs`** (editar o dono): `getPullRequestAutoMergeStatus(number)` GraphQL (`autoMergeRequest`, `isDraft`, `mergeable`, `mergeStateStatus`) + `convertPullRequestToDraft(nodeId)` (mutation `convertPullRequestToDraft`). Reusa: `createPullRequest`, `ensureAutoMergeDisabled` (laço desarme→re-poll), `getPullRequest`, `dieWithLabel`. +- **`scripts/testing-audit-disarm.mjs`** (novo, plain Node): `GITHUB_TOKEN= node … --head --title --body-file [--draft-on-failure]` cria ready → desarma → verifica; `--pr ` para rearms. Pre-check de `GITHUB_TOKEN` → `dieWithLabel` exit 1 ANTES de qualquer chamada (nada armado = fail-closed por construção). Exit 0 só com `autoMergeRequest === null && !isDraft`; imprime o JSON de status para o relatório. +- **Convention pin:** spec de convenção existente exige que a SKILL.md Fase 5 referencie o script (mecanismo classe 3). Entrada na config knip se marcado morto (família P6-G: specs não contam como usage). +- **Migration:** sem migration. **Access/Consent:** N/A. + +## Fases verificáveis + +1. **Dono** — helpers GraphQL em `github-api.mjs` + pins em `tests/unit/githubApi.unit.spec.ts` (status lido; draft; idempotência via read-first). `pnpm gate:fast`. +2. **Script + pin de convenção** — `testing-audit-disarm.mjs` (parseArgs no shape de `github-pr.mjs`; importa `dieWithLabel`). +3. **Fiação + registro** — reescrever Fase 5 da skill (1 invocação no passo que cria o PR; `--pr N` após cada push; fallback = draft automático + registro no relatório); changelog `docs/changelog/2026-08-25-audit-pass6-m909.md`; linha em `docs/GUARDRAILS.md` Ativos (origem #909, classe 3). `pnpm gate:fast`; push via `pnpm push`. + +## Rabbit holes / Não escopo + +- NÃO tocar `agent-pr-ready-automerge.yml`/`github-pr-automerge.mjs`. +- NÃO deletar `github-pr.mjs` (é P6-G, outra entrega). +- NÃO migrar o `gh run rerun` da Fase 4; NÃO re-fiar a react-audit. + +## Riscos e mitigação + +- Mutation de desarme em PR não armado → `ensureAutoMergeDisabled` só muta quando armado (idempotente). +- Token ausente → die antes de criar. Falha pós-criação → `--draft-on-failure` converte a draft (veto estrutural do safety-net → nunca mergeia). + +## Aceite de engenharia + +- [x] Aceite da intenção coberto (desarme atômico no passo de criação) +- [x] Invariantes (edita o dono `github-api.mjs`, não cria twin) +- [x] Testes: pins `githubApi.unit.spec.ts` + pin de convenção da fiação na skill + +Self-score decision-quality: 5/5 diff --git a/docs/plans/audit-pass6-perfil-page-gate-impl.md b/docs/plans/audit-pass6-perfil-page-gate-impl.md new file mode 100644 index 00000000..3885f193 --- /dev/null +++ b/docs/plans/audit-pass6-perfil-page-gate-impl.md @@ -0,0 +1,52 @@ +# Impl: P6-2 — page-gate dodge on /campanha/perfil + +Status: aprovado +Atualizado em: 2026-08-25 +Intenção: docs/plans/entrega-engenharia-p6.md +Appetite restante: 0.5 dia + +## Leitura da intenção + +- **Outcome:** `perfil/page.tsx` passa a obter o ator pelo `requireCampaignPageActor` (P3-I), e o guard de convenção deixa de ser dodge-ável por variante de nome de `getCampaignUser*`. +- **O que NÃO negociar:** sem migration, sem mudança de URL (`/campanha/perfil`), guard classe 3, líder "as good or better" (nunca tela em branco). +- **O que reavaliar:** gate `noLeader` em /perfil quebraria reset de senha e self-service do líder (`redefinir-senha/page.tsx:31`, `esqueci-senha/page.tsx:20`, `actions/password.ts:137` redirecionam para `/campanha/perfil`) — **sem gate** (precedente `meus-contatos/page.tsx:16`). + +## Abordagem recomendada + +**Opções consideradas:** A | B | C +**Recomendação:** **C** — `requireCampaignPageActor` ganha opção `withAvatar?: boolean`; a página chama só o helper e o guard alarga o regex sem allowlist. +**Rejeitadas:** A (mantém a chamada variante na página — exige allowlist + 1 leitura extra por request); B (manter só `getCampaignUserWithAvatar` + redirect hand-rolled — perpetua a divergência P3-I). + +### Componentes / mudanças + +- **`requireCampaignPageActor`** (`src/utilities/campaignPageActor.ts`): opção `withAvatar?: boolean` — `const getter = options.withAvatar ? getCampaignUserWithAvatar : getCampaignUser`. Ambos retornam `AuthenticatedCampaignUser | null`; avatar só populado em runtime. Comentário justificando (shell de perfil renderiza `avatarUrl`; avatar depth-0 vira número ID). Atualizar doc-block das gates. +- **`perfil/page.tsx`**: `requireCampaignPageActor({ withAvatar: true })`; remove `redirect` manual, check `if (!user)` e import de `getCampaignUserWithAvatar`. VERIFICAR antes: nenhum outro `page.tsx` de `(app)` casa o regex alargado (`grep -rn "getCampaignUser[A-Za-z0-9]*(" src/app/\(campaign\)/campanha/\(app\) --include="page.tsx"`). +- **Guard** (`tests/unit/codebaseConventions.unit.spec.ts:528-548`): regex → `/getCampaignUser[A-Za-z0-9]*\(/` + prosa atualizada; `allowlist` continua vazia. +- **Testes:** caso unit novo no spec de gate de página/ator (`withAvatar: true` usa o getter de avatar e redireciona quando null). +- **Migration:** sem migration. **Access/Consent:** sem mudança. + +## Fases verificáveis + +1. **Helper + página** — `withAvatar` + troca na página + caso unit. +2. **Guard** — alargar regex; `pnpm vitest run tests/unit/codebaseConventions.unit.spec.ts` bare. +3. **Gates** — `pnpm gate:fast`; push via `pnpm push`. + +## Rabbit holes / Não escopo + +- Migrar o layout para `requireCampaignPageActor` (layout não é route page; fora do finding). +- Gate `noLeader` em /perfil (decisão de produto, não engenharia). +- Derivar `getCampaignUserWithAvatar` de `getCampaignUser` via `depth` opcional (refactor além do finding). + +## Riscos e mitigação + +- Dois getters, mesmo tipo de retorno — `tsc --noEmit` valida. +- Guard alargado pegando página futura legítima: fail-closed é desejado; saída = allowlist com razão. +- Regressão de avatar: `withAvatar: true` preserva o comportamento atual. + +## Aceite de engenharia + +- [x] Aceite de produto coberto (perfil alcançável por todos os roles autenticados) +- [x] Invariantes AGENTS/engineering-standards (sem migration, URL intacta, prologue único) +- [x] Testes previstos: caso unit `withAvatar` + spec de convenção verde + +Self-score decision-quality: 5/5