From 4f5a5a838c4b018a0c07ad680359507316a1842e Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Tue, 25 Aug 2026 10:53:19 -0300 Subject: [PATCH] =?UTF-8?q?docs(audit-pass6):=20format=20=E2=80=94=20prett?= =?UTF-8?q?ier=20normalization=20of=20the=20three=20artifacts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/GUARDRAILS.md | 30 ++++---- docs/IMPROVE-CODE-QUALITY-PLAN.md | 26 +++---- docs/plans/entrega-engenharia-p6.md | 102 ++++++++++++++-------------- 3 files changed, 79 insertions(+), 79 deletions(-) diff --git a/docs/GUARDRAILS.md b/docs/GUARDRAILS.md index ba18bab2..3aafa6ba 100644 --- a/docs/GUARDRAILS.md +++ b/docs/GUARDRAILS.md @@ -6,21 +6,21 @@ Ledger dos guardrails determinísticos colhidos de `kind:agent-miss` (fluxo `pnp ## Ativos -| Guarda | Origem | Classe | Mecanismo | Status | -| ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | -| `agent:ship` exige stamp de /simplify casado com HEAD; PR Ready + auto-merge + watch até MERGED | [#83](https://github.com/fsolla/teqo/issues/83) | 3 (script + hooks) | `scripts/agent-ship.mjs`, `.cursor/hooks/guard-pr-create.mjs` (bloqueia `gh pr create` manual), `.cursor/hooks/pr-merge-watchdog.mjs` (re-engata com PR aberto) | vivo desde 2026-07-31 (PR #89); miss fechada no Pass 4 | -| Alocador de municípios em specs — sem slug fixo + mutação; sem `municipio=` em e2e; regex de nome de catálogo ancorada | [#73](https://github.com/fsolla/teqo/issues/73) | 3 | `tests/unit/testMunicipalityAllocatorConventions.unit.spec.ts` (3 regras; mutações = assign/touch/create\*/`payload.update` municipality) | PR [#93](https://github.com/fsolla/teqo/pull/93) (Pass 4) | -| Sheet compartilhada por lista — chip cells exigem `CampaignListSheetProvider`; invariante dev no overlay | [#52](https://github.com/fsolla/teqo/issues/52) | 3 + 5 | `tests/unit/sharedSheetHostConventions.unit.spec.ts`; `console.error` development-only em `CampaignCellEditOverlay` | PR [#94](https://github.com/fsolla/teqo/pull/94) (Pass 4) | -| Navegação e2e assentada — goto→goto sem settle falha o build | [#54](https://github.com/fsolla/teqo/issues/54) | 3 | `tests/unit/e2eNavigationConventions.unit.spec.ts` (settle = await expect/waitForURL/click/reload/login/catch) | PR [#95](https://github.com/fsolla/teqo/pull/95) (Pass 4) | -| Readiness de autenticador virtual antes da cerimônia WebAuthn | [#53](https://github.com/fsolla/teqo/issues/53) | 3 | mesma spec (spec com `WebAuthn.addVirtualAuthenticator` deve chamar `expectCampaignBiometricsReady(`) | PR [#95](https://github.com/fsolla/teqo/pull/95) (Pass 4) | -| Convenção de navegação e2e (goto = cold load/contrato; in-shell = continuidade) | [#54](https://github.com/fsolla/teqo/issues/54) | 6 (**judgment-only**) | parágrafo em `docs/TESTING.md` | PR [#95](https://github.com/fsolla/teqo/pull/95) (Pass 4) | -| Update de globals públicos nunca "qualquer autenticado" | P0 do Pass 4 (sem issue) | 3 | `tests/unit/globalAccessConventions.unit.spec.ts` ban `Boolean(user)` em `src/globals` | PR [#91](https://github.com/fsolla/teqo/pull/91) (Pass 4) | -| Loaders de notificação tipados pelo ator (recipientID arbitrário irrepresentável) | P0 adjacente do Pass 4 | 1 | assinatura `(payload, user: CampaignUser)` + `overrideAccess: false` sob `canReadOwnNotifications` | PR [#91](https://github.com/fsolla/teqo/pull/91) (Pass 4) | -| Die-helpers da CLI importados onde usados; `DAY_MS` single-source | P1 do Pass 4 (sem issue) | 3 | `tests/unit/scriptCliConventions.unit.spec.ts` | PR [#92](https://github.com/fsolla/teqo/pull/92) (Pass 4) | -| `src/lib` não type-importa `@/utilities/**` (direção única também para tipos) | dodge do Pass 3 (suggestionCatalog) | 2 | ESLint `no-restricted-imports` `allowTypeImports: false` na zona `src/lib` | PR [#92](https://github.com/fsolla/teqo/pull/92) (Pass 4) | -| Local API com `user` sempre declara `overrideAccess`; import dinâmico de módulos server banido em utilities | guarda ausente (medida baseline 59/0) | 3 | `tests/unit/localApiOverrideAccessConventions.unit.spec.ts` | PR [#92](https://github.com/fsolla/teqo/pull/92) (Pass 4) | -| Desarme determinístico do auto-merge da /testing-audit (cria→desarma→verifica atômico; draft fail-closed) | [#909](https://github.com/fsolla/teqo/issues/909) | 3 (script + spec) | `scripts/testing-audit-disarm.mjs` (create/`--pr` + `--draft-on-failure`); pin `tests/unit/testingAuditDisarmConventions.unit.spec.ts` (skill /testing-audit deve referenciar o script) | vivo desde 2026-08-25 (Pass 6) | -| Plano de intenção com decisão de dados grava os valores literais (IDs, nomes, envs, thresholds) — nunca só narrativa | [#895](https://github.com/fsolla/teqo/issues/895) | 6 (judgment-only declarada — doc/convenção, não fingir que doc é guarda) | bloco "Dados da decisão (literais)" no `intention-template.md` + prompt "decisão de dados → valores literais presentes no plano?" no gate do plan-issue | vivo desde 2026-08-25 (Pass 6) | +| Guarda | Origem | Classe | Mecanismo | Status | +| ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | +| `agent:ship` exige stamp de /simplify casado com HEAD; PR Ready + auto-merge + watch até MERGED | [#83](https://github.com/fsolla/teqo/issues/83) | 3 (script + hooks) | `scripts/agent-ship.mjs`, `.cursor/hooks/guard-pr-create.mjs` (bloqueia `gh pr create` manual), `.cursor/hooks/pr-merge-watchdog.mjs` (re-engata com PR aberto) | vivo desde 2026-07-31 (PR #89); miss fechada no Pass 4 | +| Alocador de municípios em specs — sem slug fixo + mutação; sem `municipio=` em e2e; regex de nome de catálogo ancorada | [#73](https://github.com/fsolla/teqo/issues/73) | 3 | `tests/unit/testMunicipalityAllocatorConventions.unit.spec.ts` (3 regras; mutações = assign/touch/create\*/`payload.update` municipality) | PR [#93](https://github.com/fsolla/teqo/pull/93) (Pass 4) | +| Sheet compartilhada por lista — chip cells exigem `CampaignListSheetProvider`; invariante dev no overlay | [#52](https://github.com/fsolla/teqo/issues/52) | 3 + 5 | `tests/unit/sharedSheetHostConventions.unit.spec.ts`; `console.error` development-only em `CampaignCellEditOverlay` | PR [#94](https://github.com/fsolla/teqo/pull/94) (Pass 4) | +| Navegação e2e assentada — goto→goto sem settle falha o build | [#54](https://github.com/fsolla/teqo/issues/54) | 3 | `tests/unit/e2eNavigationConventions.unit.spec.ts` (settle = await expect/waitForURL/click/reload/login/catch) | PR [#95](https://github.com/fsolla/teqo/pull/95) (Pass 4) | +| Readiness de autenticador virtual antes da cerimônia WebAuthn | [#53](https://github.com/fsolla/teqo/issues/53) | 3 | mesma spec (spec com `WebAuthn.addVirtualAuthenticator` deve chamar `expectCampaignBiometricsReady(`) | PR [#95](https://github.com/fsolla/teqo/pull/95) (Pass 4) | +| Convenção de navegação e2e (goto = cold load/contrato; in-shell = continuidade) | [#54](https://github.com/fsolla/teqo/issues/54) | 6 (**judgment-only**) | parágrafo em `docs/TESTING.md` | PR [#95](https://github.com/fsolla/teqo/pull/95) (Pass 4) | +| Update de globals públicos nunca "qualquer autenticado" | P0 do Pass 4 (sem issue) | 3 | `tests/unit/globalAccessConventions.unit.spec.ts` ban `Boolean(user)` em `src/globals` | PR [#91](https://github.com/fsolla/teqo/pull/91) (Pass 4) | +| Loaders de notificação tipados pelo ator (recipientID arbitrário irrepresentável) | P0 adjacente do Pass 4 | 1 | assinatura `(payload, user: CampaignUser)` + `overrideAccess: false` sob `canReadOwnNotifications` | PR [#91](https://github.com/fsolla/teqo/pull/91) (Pass 4) | +| Die-helpers da CLI importados onde usados; `DAY_MS` single-source | P1 do Pass 4 (sem issue) | 3 | `tests/unit/scriptCliConventions.unit.spec.ts` | PR [#92](https://github.com/fsolla/teqo/pull/92) (Pass 4) | +| `src/lib` não type-importa `@/utilities/**` (direção única também para tipos) | dodge do Pass 3 (suggestionCatalog) | 2 | ESLint `no-restricted-imports` `allowTypeImports: false` na zona `src/lib` | PR [#92](https://github.com/fsolla/teqo/pull/92) (Pass 4) | +| Local API com `user` sempre declara `overrideAccess`; import dinâmico de módulos server banido em utilities | guarda ausente (medida baseline 59/0) | 3 | `tests/unit/localApiOverrideAccessConventions.unit.spec.ts` | PR [#92](https://github.com/fsolla/teqo/pull/92) (Pass 4) | +| Desarme determinístico do auto-merge da /testing-audit (cria→desarma→verifica atômico; draft fail-closed) | [#909](https://github.com/fsolla/teqo/issues/909) | 3 (script + spec) | `scripts/testing-audit-disarm.mjs` (create/`--pr` + `--draft-on-failure`); pin `tests/unit/testingAuditDisarmConventions.unit.spec.ts` (skill /testing-audit deve referenciar o script) | vivo desde 2026-08-25 (Pass 6) | +| Plano de intenção com decisão de dados grava os valores literais (IDs, nomes, envs, thresholds) — nunca só narrativa | [#895](https://github.com/fsolla/teqo/issues/895) | 6 (judgment-only declarada — doc/convenção, não fingir que doc é guarda) | bloco "Dados da decisão (literais)" no `intention-template.md` + prompt "decisão de dados → valores literais presentes no plano?" no gate do plan-issue | vivo desde 2026-08-25 (Pass 6) | ## Precedentes vivos (anteriores ao primeiro harvest) diff --git a/docs/IMPROVE-CODE-QUALITY-PLAN.md b/docs/IMPROVE-CODE-QUALITY-PLAN.md index aab11b40..d4abc37d 100644 --- a/docs/IMPROVE-CODE-QUALITY-PLAN.md +++ b/docs/IMPROVE-CODE-QUALITY-PLAN.md @@ -277,23 +277,23 @@ Statuses: pending · in-progress · awaiting-evidence · done · deferred: \` + ban ESLint em tests | 2 — ESLint `no-restricted-syntax` em tests | 21 arquivos de spec | -| P6-R | Guard de actor-field invertido: qualquer `relationTo: 'campaignUser'` não vindo da factory falha (não só os 3 nomes) | 2/3 — spec | `codebaseConventions.unit.spec.ts:571` | -| P6-U | `peopleData`/`personDelete` split (facets/sort vs loader; enumeração read-only vs mutação) | 6 — judgment | 627/521 LOC | -| P6-V | AI shell: região polite anunciando resposta; usar `CampaignFormActionMessage` nos contatos | 6 — judgment (a11y) | 0 aria-live em 8 arquivos `shell/ai/` | -| P6-V2 | `campaignNavigationUrls.ts` → `lib/` (puro, client-safe) | 3 — spec de pureza top-level | 636 LOC, imports só `@/lib/*` | +| WS | Conteúdo | Guarda (classe) | Evidência | +| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| P6-A | Saved-filters ×6 → 1 trio (hook/control/nav) sobre o store compartilhado; copy como data | 3 — spec bane novo twin de saved-filter fora do dono | `usePeopleSavedFilters` 43L ↔ `useMunicipalitySavedFilters` 48L; comentário "must not be written twice" violado em `:26` | +| P6-B | Advisor-relation cells ×3 → 1 célula com record de owner-kind (precedente `DIRECTION_COPY`) | 3 — spec: arquivo `*AdvisorRelationCell`/`*AssessoradoCell` novo precisa delegar ao dono | 89/96/99 LOC, só diferem FormData field/título/label | +| P6-C | Filter-bar shells ×13 → shell com slots + adapter record (adapters já exportam a superfície de 6 fns idêntica) | 6 — judgment-only (abstração grande); gatilho: próximo shell novo | ~1.700 LOC, `StateDeputyFilters` 96L ≈ `AdvisorFilters` 103L | +| P6-D | `searchHome*` ×6 → core tipado + wrappers nomeados (padrão `runStaffEntityMutation`); cap single-source | 3 — spec: cap com nome único; skeleton ban | 847 LOC; cap ×2 nomes (25) | +| P6-E | `chipLabel` ×12 → 1 export; deletar re-spell de `filterOmniboxSuggestionSeeds` em `municipalityOmnibox.ts:401-445` | 3 — spec: `const chipLabel` permitido 1× | 12/13 arquivos | +| P6-F | Bare `86_400_000` ×12 → `DAY_MS` | 3 — `scriptCliConventions`/spec flag do literal com allowlist | 11 tests + `campaignTime.ts:134` | +| P6-G | Deletar módulos mortos (`projectionSheetParse` 173L, `searchOnlyListOmnibox` 98L) + scripts mortos (`github-pr.mjs`, `measure-e2e-family.mjs`, `regenerate-municipality-catalog-snapshot.mjs`) | 4 — knip config: spec não conta como usage (entry de testes excluído) | 0 importadores de produção | +| P6-H | Guard de CLI estendido: ban `function die(` + `process.exit(`; migrar 4 scripts para `dieWithLabel` | 3 — `scriptCliConventions` endurecida | 4 `function die()` + ~20 `process.exit(1)` | +| P6-I | `taskActions.ts` no wrapper `runCampaignFormAction`; safelists importam as constantes (`ACTIVITY_RESULT_STAFF_MESSAGE` etc.) | 2/3 — spec varre `error.message` em `(campaign)` + literal em safelist | `taskActions.ts:20-27`; `resultFormActions.ts:17` | +| P6-J | Dedup de contact nos 3 forms públicos (`findOrCreateContactByPhone` pattern) | 5 — pin int: mesma submissão 2× → 1 contact | `submitWhatsapp.ts:30`, `submitPetitionSignature.ts:40`, `submitCampaignNewsletter.ts:26` | +| P6-K | `findPublishedPosts` com `select` (projeção de listagem); related-posts derivam da mesma query | 3 — spec: `find` de post sem `select` falha | `posts.ts:102-114` sem `select` nenhum | +| P6-M | Sweep `server-only` ampliado para `next/navigation` (+ allowlist dos limpos) | 3 — `codebaseConventions` | `campaignFormActionError.ts:1` | +| P6-N | D1 trigger com dentes: spec conta N módulos top-level com prefixo de domínio; 12 `activity*` + trio `googleCalendar*` + 3 omniboxes top-level → subpastas | 3 — spec de contagem de prefixo | `src/utilities/` top-level | +| P6-O | Fold `personContactWriteScope.ts` → `people/` | 3 — spec de split-brain por prefixo | `person/` com 1 arquivo | +| P6-P | `as unknown as` → `stub` + ban ESLint em tests | 2 — ESLint `no-restricted-syntax` em tests | 21 arquivos de spec | +| P6-R | Guard de actor-field invertido: qualquer `relationTo: 'campaignUser'` não vindo da factory falha (não só os 3 nomes) | 2/3 — spec | `codebaseConventions.unit.spec.ts:571` | +| P6-U | `peopleData`/`personDelete` split (facets/sort vs loader; enumeração read-only vs mutação) | 6 — judgment | 627/521 LOC | +| P6-V | AI shell: região polite anunciando resposta; usar `CampaignFormActionMessage` nos contatos | 6 — judgment (a11y) | 0 aria-live em 8 arquivos `shell/ai/` | +| P6-V2 | `campaignNavigationUrls.ts` → `lib/` (puro, client-safe) | 3 — spec de pureza top-level | 636 LOC, imports só `@/lib/*` | ### P3 -| WS | Conteúdo | Guarda | Evidência | -| --- | --- | --- | --- | -| P6-Q | `fixtures.own()` 75 → auto-ownership; split do hub de fixtures | 3 | `campaignFixtures.ts` 1.168L | -| P6-S | Gender labels: manter fork deliberado (rejeitado como look-alike), registrar gatilho | 6 | 'Não binário' vs 'Outro' | -| P6-T | `getSignatureCount` com `cache()` | 2 | `getSignatureCount.ts:9` | -| P6-X | Migração de schema (ex.: users-roles) e URLs públicas: **fora de escopo** por construção | — | ledger | +| WS | Conteúdo | Guarda | Evidência | +| ---- | ---------------------------------------------------------------------------------------- | ------ | ---------------------------- | +| P6-Q | `fixtures.own()` 75 → auto-ownership; split do hub de fixtures | 3 | `campaignFixtures.ts` 1.168L | +| P6-S | Gender labels: manter fork deliberado (rejeitado como look-alike), registrar gatilho | 6 | 'Não binário' vs 'Outro' | +| P6-T | `getSignatureCount` com `cache()` | 2 | `getSignatureCount.ts:9` | +| P6-X | Migração de schema (ex.: users-roles) e URLs públicas: **fora de escopo** por construção | — | ledger | ## Defer + trigger registrados (sem ID próprio) -| Candidato | Motivo do defer | Gatilho de reabertura | -| --- | --- | --- | -| homeSearch int triplet → `describe.each` | 2 specs gêmeos de fixture-heavy int; restruturar carrega cada assertion verbatim | 4º irmão do triplet | -| `agent-pool.mjs` + `lib/agent-pool-*` (516L) | pool dormente (OPS65); `agent:pool` ainda tem entrada em package.json | limpeza pós-pool | -| Parsers quick-action V1/V2 | B147 paralelo deliberado (ledger, issue #335) | cutover do v2 | -| `municipio/[slug]/v2` prologue `cache()` | mesma classe P5-D — a rota v2 é a pior instância; corrigir junto do P5-D | — | -| `measure-e2e-family.mjs` | ferramenta OPS87 com docs próprias | deletar junto das docs do workflow | +| Candidato | Motivo do defer | Gatilho de reabertura | +| -------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------- | +| homeSearch int triplet → `describe.each` | 2 specs gêmeos de fixture-heavy int; restruturar carrega cada assertion verbatim | 4º irmão do triplet | +| `agent-pool.mjs` + `lib/agent-pool-*` (516L) | pool dormente (OPS65); `agent:pool` ainda tem entrada em package.json | limpeza pós-pool | +| Parsers quick-action V1/V2 | B147 paralelo deliberado (ledger, issue #335) | cutover do v2 | +| `municipio/[slug]/v2` prologue `cache()` | mesma classe P5-D — a rota v2 é a pior instância; corrigir junto do P5-D | — | +| `measure-e2e-family.mjs` | ferramenta OPS87 com docs próprias | deletar junto das docs do workflow | ## Rejeitados como look-alike (não re-propor) @@ -67,14 +67,14 @@ Ordenados por severidade; dentro dela, por blast radius crescente. Teto do Pass ## Guard map do Pass -| Classe de achado | Guarda | Mecanismo | Status | -| --- | --- | --- | --- | -| Bypass admin indocumentado | P3-E ratchet | `codebaseConventions` por-arquivo, só diminui | **endurecida P6-1** (comentário de módulo não zera; novos arquivos pinados) | -| Ator de página hand-rolled | P3-I | regex em page.tsx de `(app)` | **endurecida P6-2** (variantes `getCampaignUser*`) | -| Automerge armado em PR de audit | OPS98 veto + #909 | safety-net pula `audit/*`; script de desarme atômico | **nova P6-M909** | -| Plano de intenção sem dados literais | #895 convenção | skill `plan-issue` | **nova P6-M895** (classe 6 declarada) | -| `as never` twin (`as unknown as`) | P6-P | ESLint em tests | planejada | -| Literal `86_400_000` | P6-F | spec com allowlist | planejada | -| `function die(`/`process.exit(` em scripts | P6-H | `scriptCliConventions` estendida | planejada | -| Módulo morto sustentado por spec | P6-G | knip config | planejada | -| Actor-field fora da factory | P6-R | spec invertida (nome-independente) | planejada | +| Classe de achado | Guarda | Mecanismo | Status | +| ------------------------------------------ | ----------------- | ---------------------------------------------------- | --------------------------------------------------------------------------- | +| Bypass admin indocumentado | P3-E ratchet | `codebaseConventions` por-arquivo, só diminui | **endurecida P6-1** (comentário de módulo não zera; novos arquivos pinados) | +| Ator de página hand-rolled | P3-I | regex em page.tsx de `(app)` | **endurecida P6-2** (variantes `getCampaignUser*`) | +| Automerge armado em PR de audit | OPS98 veto + #909 | safety-net pula `audit/*`; script de desarme atômico | **nova P6-M909** | +| Plano de intenção sem dados literais | #895 convenção | skill `plan-issue` | **nova P6-M895** (classe 6 declarada) | +| `as never` twin (`as unknown as`) | P6-P | ESLint em tests | planejada | +| Literal `86_400_000` | P6-F | spec com allowlist | planejada | +| `function die(`/`process.exit(` em scripts | P6-H | `scriptCliConventions` estendida | planejada | +| Módulo morto sustentado por spec | P6-G | knip config | planejada | +| Actor-field fora da factory | P6-R | spec invertida (nome-independente) | planejada |