From 067857a03a119b2ccd7d1462b242978dd41985e6 Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Wed, 16 Sep 2026 19:51:05 -0300 Subject: [PATCH] =?UTF-8?q?fix(OPS124):=20desabilita=20o=20plugin=20Payloa?= =?UTF-8?q?d=20do=20knip=20e=20explicita=20as=20exclus=C3=B5es=20(Closes?= =?UTF-8?q?=20#1094)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit O `pnpm knip` imprimia `ERROR: Error loading src/payload.config.ts (...server-only...)` e saía 0 — gate verde com o ERROR silencioso, no CI (`ci-pr.yml`/`deploy.yml`) e local. O plugin embutido do Payload carregava `src/payload.config.ts` via jiti sob as condições Node padrão e o import transitivo de `server-only` lançava (só o `react-server` traz o `empty.js`); o `paths.server-only` do `knip.json` alimenta apenas o resolver estático, não o carregamento jiti (e o `tsconfig` alias, que funcionaria no load, foi rejeitado por re-apontar a guarda server/client — classe OPS69). Fix: `"payload": false` e as contribuições do plugin explicitadas no `knip.json` (`!src/payload-types.ts` load-bearing; `!src/migrations/**` paridade), removendo o bloco `paths` inerte. Provado: `pnpm knip` sai 0 sem o ERROR, um export morto plantado é detectado, e a corrida de controle com `server-only` neutralizado dá findings idênticos. Plano: docs/plans/ops-knip-importmap-artifact-impl.md --- .agents/rules/engineering-standards.mdc | 2 +- .agents/skills/engineering-audit/SKILL.md | 2 +- AGENTS-infra.md | 2 +- docs/TECH-DEBT.md | 2 +- docs/changelog/2026-09-16-ops124.md | 1 + .../plans/ops-knip-importmap-artifact-impl.md | 87 +++++++++++++++++++ docs/plans/ops-knip-importmap-artifact.md | 8 +- knip.json | 14 +-- 8 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 docs/changelog/2026-09-16-ops124.md create mode 100644 docs/plans/ops-knip-importmap-artifact-impl.md diff --git a/.agents/rules/engineering-standards.mdc b/.agents/rules/engineering-standards.mdc index 244b757c..d239a326 100644 --- a/.agents/rules/engineering-standards.mdc +++ b/.agents/rules/engineering-standards.mdc @@ -12,7 +12,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` **sem e2e** — OPS72: o e2e é passo **explícito da skill** (`work-issue`/`agent-work-issue`): antes do push rode localmente os e2e que criou + os da mesma superfície, **discricionário** via `pnpm test:e2e:affected`; o CI de PR roda só o blast radius `selected` — nunca `full` — e o full fica no `verify` do deploy (o run nasce no merge — OPS104); **com os `docs-guards` locais** — OPS63/OPS85: changelog additions-only + HISTORY congelado + agregado não-commitado, e marcadores de conflito, mesmos scripts do CI; o escape `changelog-rewrite:` fica só no CI, body do PR). 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; 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`; publish gated pelo `verify` do `deploy.yml` (início automático no merge, approve humano na produção — OPS104; não há `ci.yml` verificador de main). **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). +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's graph is deliberately scoped: the Payload plugin is disabled — `knip.json` `"payload": false`, OPS124 — because it could never load `payload.config.ts` under default Node conditions, so `--fix` can delete exports migrations/tests still reference). ## Type honesty diff --git a/.agents/skills/engineering-audit/SKILL.md b/.agents/skills/engineering-audit/SKILL.md index 1715a6fc..f10e6d34 100644 --- a/.agents/skills/engineering-audit/SKILL.md +++ b/.agents/skills/engineering-audit/SKILL.md @@ -94,7 +94,7 @@ Fases pesadas rodam em sub-agentes com contexto mínimo e output limitado; o age 1. Read-only em `src/`, `tests/`, `scripts/` durante a VARREDURA. Writes da noite: os três artefatos, os `-impl` das melhorias elegíveis e suas implementações na branch do Pass. 2. Gate commands bare, never piped (`pnpm test | tail` swallows the exit code). -3. Never `knip --fix` blind — verify with `git grep -w ` (knip cannot load `payload.config.ts`; ledgered P3). +3. Never `knip --fix` blind — verify with `git grep -w ` (knip's graph is deliberately scoped: `knip.json` `"payload": false`, OPS124 — the Payload plugin could not load `payload.config.ts` because `server-only` throws in knip's non-RSC loader; `--fix` can delete exports migrations/tests still reference). 4. Production is live Postgres on the homeserver (`teqo_1313`) with real PII. Local DB only, `teqo_test` for tests. The audit needs no DB writes. 5. Every claim gets a number (lines, exports, call sites, ms, kB). "Rejected by measurement" is an acceptable outcome. 6. Frozen migrations never edited; schema change = `pnpm migrate:create` (e sai do escopo da noite — ver teto). diff --git a/AGENTS-infra.md b/AGENTS-infra.md index dfbe7240..9dd80e26 100644 --- a/AGENTS-infra.md +++ b/AGENTS-infra.md @@ -21,7 +21,7 @@ Production is self-hosted on the homeserver (`jorgesolla1313.com.br`, behind the 1. Confirm `DATABASE_URL` is `postgresql://teqo:teqo@localhost:5432/teqo` and `.env.test` points to `teqo_test`; never use the homeserver prod DB for development, tests, E2E, or local builds. In a provisioned worktree (`pnpm worktree next`) the values come from its `.env.local` / `.env.test.local` (`teqo_wt` / `teqo_wt_test`, dev port `3100+slot`) — read them there instead of assuming the main-repo names. 2. Run `pnpm db:start`, `pnpm migrate:status`, and `DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo_test pnpm migrate:status`; every migration must show `Ran: Yes`. 3. Run `pnpm generate:types`, `pnpm generate:importmap` when components changed — **com as envs `S3_*` setadas (valores dummy ok) enquanto o plugin `s3Storage` for condicional: sem elas o importMap fica órfão do `S3ClientUploadHandler` e o admin abre em branco em produção (OPS69, P0 em 2026-08-19)** —, `pnpm exec tsc --noEmit`, `pnpm lint` (zero warnings — the script enforces `--max-warnings=0`), `pnpm format:check` (Prettier is the formatting authority; `pnpm format` fixes), `pnpm exec knip` (dead files/dependencies are CI-blocking; delete what your change orphaned), `pnpm check:cycles` (madge — zero circular imports), `pnpm test` (unit + int), and `pnpm build` against the local database. Payload CLI scripts (`migrate`, `generate:*`, `payload`) and the build migrate step use `--conditions=react-server` so `server-only` modules load correctly outside Next.js. **Política de e2e (OPS72 + OPS86):** o e2e **não roda no gate local** (`gate:push`) — é passo **explícito da skill** (`work-issue`/`agent-work-issue`): antes do push o agente roda localmente os e2e que **criou** + os da **mesma superfície afetada** — **discricionário** (o agente decide quais; ferramenta: `pnpm test:e2e:affected`, espelho local do classifier — gera `--no-deps` para runs filtrados, S6-FOLLOWUP: no dev mode a cadeia de dependencies dos projects arrasta os arquivos das dependências via closure; filtros manuais locais: `pnpm test:e2e --no-deps -- ` ou `--no-deps --project=`; limitação #72: `--no-deps` + projetos paralelos colide no `seedTestUser` → `--workers=1` ou a cadeia padrão de projetos). O **CI de PR roda apenas o blast radius** (via `ci-scope.mjs` — **nunca `full`**): diff mapeado roda `selected`, diff **high-risk** (coleções/globals/migrações/lockfile/harness) roda o **`curated`** — conjunto fixo de 5 specs (`campaignPermissionProfile`, `campaignDemandVisibility`, `campaignAiTranscribe`, `campaignAgendaFeed`, `campaignNewsletter`, congelado por pin em `e2eAffectedManifest.unit.spec.ts`) — **nunca zero e2e no PR**; arquivo de **área de risco sem entry no manifesto** (`src/utilities/access`, `src/lib/schemas`, `campaignPushClient`, `src/utilities/ai`) classifica **`unmapped-risk` e o PR FALHA** listando os arquivos (fail-closed; resolver = adicionar entry no `E2E_AFFECTED_MANIFEST`); src/ não-mapeado não-risco roda o smoke `campaignHomeActions`. Os passos unit/int `changed` passam por `scripts/vitest-changed-or-full.mjs`: seleção vazia cai em **suíte full** — nunca "0 testes, verde". O e2e **full** roda no `verify` do deploy (o run nasce no merge — OPS104; a produção só publica com o approve do reviewer — gateia merge e deploy, rede de segurança final antes de publicar); localmente, diff high-risk segue rodando full no `pnpm test:e2e:affected`. - - Never run `knip --fix` blind: when knip's module graph is incomplete (it still cannot load `payload.config.ts` — ledgered as P3), `--fix` deletes exports that migrations/tests still reference. Verify each removal (e.g. `git grep -w `) before deleting. + - Never run `knip --fix` blind: knip's module graph is deliberately scoped (`"payload": false` — OPS124 removed the Payload plugin because it could not load `payload.config.ts`: `server-only`'s default entry throws under knip's non-RSC loader, and aliasing it would mean re-pointing `server-only` in the shared `tsconfig.json`, rejected as an OPS69-class risk), so `--fix` can delete exports that migrations/tests still reference. Verify each removal (e.g. `git grep -w `) before deleting. - Run gate commands bare — never piped (`pnpm test | tail` swallows the exit code, and a real failure was masked exactly this way during Pass 2). Large outputs are captured by the terminal; read them from there. 4. Scan every edited runtime source file with Aikido and resolve new findings before handoff. Do not expand hardening work into unrelated pre-existing findings without an explicit scope change. 5. Review production environment variables and migration SQL before deploy; `pnpm build` applies pending migrations to the deployed database automatically. **`NEXT_PUBLIC_SITE_URL` (ex.: `https://jorgesolla1313.com.br`) é obrigatória em Production** — sem ela, `getCampaignInviteBaseURL()` lança em runtime e o fluxo de convites/webauthn/link de import da agenda falha com mensagem genérica (C98, 2026-08-09: produção ficou sem a env e "gerar link" do feed quebrava; o preview tinha a de stage). Em staging (OPS103) a env carrega `https://staging.jorgesolla1313.com.br` — o mesmo fail-closed vale e a origem `staging.` também desliga o index (`robots.ts` + metadata). diff --git a/docs/TECH-DEBT.md b/docs/TECH-DEBT.md index d519a509..5ed866f8 100644 --- a/docs/TECH-DEBT.md +++ b/docs/TECH-DEBT.md @@ -7,7 +7,7 @@ Ledger of known engineering debt. Items large enough to be scheduled work become | Item | Location | Type | Risk | Effort | Priority | Status | Ref | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | | 164 unused exports + 60 unused types reported by knip (warn-level; 158 at Pass 1) | god modules, ui kit families | dead code | low | M | P2 | closed 2026-07-25 (Pass 2 W4b): 0 findings; `exports`/`types`/`enumMembers` at **error** in CI; tests entered the knip graph; `duplicates` off (only hits were the 6 deliberate access aliases `canUpdateX = canReadX`) | Pass 2 W4b | -| knip loader noise: `Error loading src/payload.config.ts` (server-only guard) | knip run output | tooling noise | none (exit 0, static graph resolves) | S | P3 | open | | +| knip loader noise: `Error loading src/payload.config.ts` (server-only guard) | knip run output | tooling noise | none funcional (exit 0 mascarava o ERROR; a config nunca carregava) | S | P3 | closed 2026-09-16 (OPS124): plugin Payload do knip desabilitado (`"payload": false`) e as exclusões de projeto promovidas ao `knip.json` (`!src/migrations/**`, `!src/payload-types.ts`); o `ERROR: Error loading src/payload.config.ts` some. `knip:production` segue fora de escopo (32 unused-deps pré-existentes). | | | Aikido scan unusable: `aikido_full_scan` answers Opengrep **exit code 2** for any input, including a one-line probe file, so the per-change security scan the rules mandate cannot complete. What it reported is an artifact of the failed run, not attributable to the code under review; the Aikido feed lists nothing for this repo. The `shpjs` devDependency added by B8 F2 is therefore SCA-unverified | Aikido MCP / local scanner | tooling outage (security gate) | medium — the gate is skipped, not passed | S | P2 | recovered 2026-07-27 (B34): `aikido_full_scan` completed with 0 findings on the changed files; only taint analysis timed out. **Exit 2 did return in the same day's follow-up pass, and the useful detail is that it is not a total outage:** the wrapper reports the error while Opengrep's own summary says `Ran 316 rules on 1 file: 0 findings`, so SAST results are present and clean — the exit code tracks "some files were skipped or only partially analyzed", not a failed scan. Read the summary text before treating a red scan as a blocked gate. The SCA half (incl. `shpjs`) remains unverified | | | Missing-consent window fixture releases its exclusive lease during the observation window — readers can interleave (rare flake). Full fix: session-level advisory lock spanning delete→op→restore + updated fault-path pins | `tests/helpers/testDatabaseLease.ts` (`withMissingInviteConsentFixture`) | test-infra race | low (ms-wide window; shared leases added 2026-07-23 narrow it) | M | P2 | open | | | `users` collection has no roles (all admins omnipotent) | `src/collections/Users.ts` | RBAC | medium (small trusted admin set) | M (schema migration) | P1 before widening `/admin` | closed 2026-07-27 (`admin`\|`editor`; migration `20260727_032523_add_users_roles`; [roles-admin-payload.md](plans/roles-admin-payload.md)) | AGENTS Known Gaps #1 | diff --git a/docs/changelog/2026-09-16-ops124.md b/docs/changelog/2026-09-16-ops124.md new file mode 100644 index 00000000..3d9fbdb8 --- /dev/null +++ b/docs/changelog/2026-09-16-ops124.md @@ -0,0 +1 @@ +**OPS124 — knip para de carregar o `payload.config.ts` e some o ERROR silencioso (2026-09-16):** o `pnpm knip` imprimia `ERROR: Error loading src/payload.config.ts (This module cannot be imported from a Client Component module...)` e saía **0** — gate verde com um ERROR que ninguém lia, no CI (`ci-pr.yml`/`deploy.yml`) e local. Causa-raiz (correção de rumo em relação à primeira descrição): o plugin embutido do Payload (`node_modules/knip/dist/plugins/payload`) carrega `src/payload.config.ts` via jiti sob as condições Node padrão, e o import transitivo de `server-only` resolve para o entry que **lança** (só o `react-server` devolve o `empty.js`); o `paths.server-only` do `knip.json` alimenta apenas o resolver estático do knip, não o carregamento jiti. O ERROR é **independente** do `importMap.js`: sem o arquivo, o knip sai 1 com `Unresolved imports (2) ../importMap` + os componentes admin órfãos; com o arquivo commitado, o mesmo ERROR sai 0 (o arquivo só trocava o relatório pelo exit 0). Corrigido por A: **desabilitar o plugin do Payload** (`"payload": false`) — ele só derivava o importMap, que já é `entry` explícito, e as exclusões de projeto — promovendo `!src/payload-types.ts` (load-bearing: sem ela, 74 "unused exported types" espúrios) e `!src/migrations/**` (paridade com o escopo de história congelada) para o `project`, e removendo o bloco `paths` inerte. Rejeitados: alias de `server-only` no `tsconfig` (funcionaria no load — o jiti do knip usa `tsconfigPaths: true` — mas re-apontaria a guarda para um no-op na toolchain inteira, risco da classe OPS69) e destrackear o importMap (não remove a causa e reabre o admin em branco). Provado: `pnpm knip` sai 0 sem o ERROR, um export morto plantado é detectado, e uma corrida de controle com `server-only` neutralizado para o plugin carregar produz findings idênticos. Fecha a linha do `docs/TECH-DEBT.md`; `knip:production` segue fora de escopo (32 unused-deps pré-existentes). Sem schema, sem UI, sem deploy. Issue #1094. diff --git a/docs/plans/ops-knip-importmap-artifact-impl.md b/docs/plans/ops-knip-importmap-artifact-impl.md new file mode 100644 index 00000000..43f72c14 --- /dev/null +++ b/docs/plans/ops-knip-importmap-artifact-impl.md @@ -0,0 +1,87 @@ +# Impl: OPS: knip carrega o payload.config.ts com erro (importMap commitado) e roda com análise degradada + +Status: aprovado +Atualizado em: 2026-09-16 +Issue: #1094 +Intenção: docs/plans/ops-knip-importmap-artifact.md +Appetite restante: herdado — ~30 min eng; sem schema, sem deploy, sem UI (Impeccable A) + +## Leitura da intenção + +- **Outcome:** `pnpm knip` deixa de imprimir `ERROR: Error loading src/payload.config.ts (This module cannot be imported from a Client Component module...)` na stderr e a análise continua completa — um export morto plantado segue sendo detectado. Hoje o erro aparece e o comando sai 0, gate verde com grafo degradado. +- **O que NÃO negociar:** a cobertura de análise (nenhum finding novo/ausente por causa do fix), a suíte de gates inalterada em ordem e comportamento, e nenhum risco à classe OPS69 (admin em branco) — `importMap.js` permanece commitado e o `entry` explícito permanece. +- **O que reavaliar:** a hipótese da intenção de que `paths.server-only` "não está sendo aplicado" está correta, mas a correção de rumo é outra: `paths` alimenta o resolver estático do knip, não o carregamento jiti do `payload.config.ts`; e destrackear o `importMap.js` (opção b da intenção) não remove o ERROR. Ambas caem. + +## Abordagem recomendada + +```mermaid +flowchart TD + A["pnpm knip"] --> B{"payload plugin enabled?"} + B -- sim --> C["jiti carrega src/payload.config.ts
(condições Node default)"] + C --> D["transitive import de server-only
exports[.].default = index.js"] + D --> E["throw: cannot be imported from a Client Component"] + E --> F["ERROR na stderr + exit 0 (grafo sem as entradas derivadas do config)"] + B -- "não (payload:false)" --> G["config globs e resolveConfig
deixam de ser injetados"] + G --> H["importMap já é entry explícito
+ negations project explícitas"] + H --> I["pnpm knip: sem ERROR, grafo completo"] + I --> J["export morto plantado é detectado
(Unused exports 1)"] +``` + +**Opções consideradas:** A (desabilitar o plugin Payload e explicitar o que ele contribuía) | B (manter o plugin com globs neutralizados) | C (tornar o config carregável via alias/condição) | D (destrackear `importMap.js`) + +**Recomendação:** A — `"payload": false` em `knip.json`, promover a contribuição útil do plugin para o próprio `knip.json` (o `entry` do importMap já existe; adicionar `"!src/migrations/**"` e `"!src/payload-types.ts"` ao `project`) e remover o bloco `paths` inerte. O plugin só contribui com (a) os globs de config, (b) um `resolveConfig` que devolve o importMap como entry diferido — redundante, pois o importMap já é `entry` explícito — e (c) as exclusões de projeto. Ou seja, desligá-lo e escrever (a)+(c) à mão preserva a análise sem manter um plugin que nunca consegue carregar o config. Prova empírica (config temporário): exit 0 sem ERROR e sem findings; sonda `export const __knipDeadProbe = 1` em `src/lib/campaignTime.ts` detectada (`Unused exports (1)`); corrida de controle neutralizando `node_modules/server-only/index.js` para permitir o carregamento pelo plugin produziu findings idênticos (`exit 0`, nenhum) — no estado atual, zero perda de findings. Das duas exclusões, **só `!src/payload-types.ts` é load-bearing**: sem ela, `payload-types.ts` gera 74 "unused exported types" espúrios; `!src/migrations/**` reproduz a exclusão de história congelada do plugin e é redundante hoje (as migrations já são `entry`), mantida por paridade de escopo. + +**Rejeitadas:** + +- **B** — obscuro, mantém um plugin que nunca carregará o config e ainda exige as negações escritas à mão; não há ganho sobre A. +- **C** — o alias de `server-only` **funcionaria** no carregamento: o jiti do knip (`node_modules/knip/dist/util/jiti.js`) usa `tsconfigPaths: true`, então `compilerOptions.paths` no `tsconfig.json` resolveria para o stub e o config carregaria (verificado). Rejeitada mesmo assim porque re-aponta `server-only` para um no-op em toda a toolchain (tsc/bundler), esvaziando a própria guarda de fronteira server/client e arriscando a classe OPS69 (admin em branco); `NODE_OPTIONS=--conditions=react-server` e o `paths` do `knip.json` não atingem o load (testados). O alias existe hoje só escopado ao vitest — mantê-lo escopado é a diferença. +- **D** — não remove o ERROR (causa independente: ele também ocorre sem o arquivo); sem o importMap o knip sai 1 com `Unresolved imports (2) ../importMap` + `Unused files (2)` dos componentes admin; e reabre o risco OPS69 que motivou commitá-lo (`fea6c645`/`b03cec7c`). + +### Componentes / mudanças + +- **`knip.json`** (bloco `paths`, removido nesta mudança): o mapeamento `server-only` não afeta o carregamento jiti — era a tentativa equivocada de correção; remover deixa o config honesto. +- **`knip.json`**: adicionar `"payload": false` (desabilita o plugin embutido, que hoje importa `src/payload.config.ts` via jiti). +- **`knip.json`** (`project`): adicionar `"!src/migrations/**"` e `"!src/payload-types.ts"` — reproduzem as exclusões que o plugin injetava. Só a segunda é load-bearing (sem ela, 74 findings espúrios em `payload-types.ts`); a primeira espelha o escopo de história congelada do plugin. +- **`knip.json`** (`entry`): inalterado — o importMap já é um `entry` explícito, o que torna o `resolveConfig` do plugin redundante. +- **`docs/TECH-DEBT.md:10`**: fechar a linha do ledger (impacto/status → `closed 2026-09-16 (OPS124)`). +- **`docs/changelog/2026-09-16-ops124.md`**: entrada nova exigida pelos docs-guards. +- **`docs/plans/ops-knip-importmap-artifact.md`**: atualizar status da intenção. +- Demais docs com a P3 obsoleta (`AGENTS-infra.md:24`, `.agents/rules/engineering-standards.mdc:15`, `.agents/skills/engineering-audit/SKILL.md:97`): corrigir a referência para não manter a dívida "open". + +### Dados → forma (se aplicável) + +Não se aplica — nenhuma mudança de schema, coleção, global ou migração. A mudança é apenas de configuração de tooling (`knip.json`) e de documentação do ledger. + +## Fases verificáveis + +1. **knip.json + prova local** — aplicar A (`payload: false`, negações no `project`, remover `paths`); rodar `pnpm knip` e confirmar exit 0 sem `Error loading src/payload.config.ts`; plantar `export const __knipDeadProbe = 1` em `src/lib/campaignTime.ts` e confirmar que ele é reportado; remover a sonda. +2. **Fechamento de ledger + changelog + gate** — fechar `docs/TECH-DEBT.md:10`, ajustar as referências P3 obsoletas, criar `docs/changelog/2026-09-16-ops124.md`; rodar `pnpm gate:fast` e depois `pnpm push`. + +## Rabbit holes / Não escopo (engenharia) + +- **Ordem do CI** — knip antes do build já é o caso; não alterar pipelines. +- **`knip:production`** (`package.json`) — não referenciado no CI; já sai 1 com 32 unused-dependencies pré-existentes sob a config atual E a corrigida. Fora de escopo; apenas registrar o estado. +- **Outros pontos do `knip.json`** (`ignore`, `ignoreBinaries`, `ignoreDependencies`, `rules`) — não mexer. +- **Status de commit do `importMap.js`** — permanece commitado (decisão OPS69/OPS99 em sentido inverso); não destrackear. +- **`server-only` / vitest alias** — o alias de teste (`vitest.config.mts`, `vitest.unit.config.mts`) fica como está; não é o caminho do fix. + +## Riscos e mitigação + +- **Desligar o plugin esconde futuras entries derivadas do config** — mitigado mantendo o importMap como `entry` explícito e documentando a decisão; o plugin hoje não adiciona collections/globals/components, então não há perda atual. +- **As exclusões do `project` passam a ser nossas** — atualizações futuras do plugin Payload que mexam no `project` default (`node_modules/knip/dist/plugins/payload/index.js`) deixam de se aplicar em silêncio. Não há como manter `plugin.project` com o plugin configurado por objeto (`normalizePluginConfig`/`getPluginProjectFilePatterns` descartam o default quando um config é dado), então a paridade é manual por natureza; o gatilho de revisão é o upgrade do knip. +- **Classificação do CI** — `scripts/lib/test-affected-core.mjs` lista `knip.json` em `CODE_CONFIG_EXACT`, então o diff força `code_mode=code` e o knip É exercitado no `ci-pr.yml` e no `deploy.yml`; nada a mudar. +- **Impacto em runtime/produção** — nenhum: knip é dev-only; o build e o admin não são tocados. +- **Regressão de cobertura de análise** — validada pela sonda de export morto + corrida de controle com o plugin forçado a carregar (findings idênticos). + +## Aceite de engenharia + +- [x] `pnpm knip` sai 0 sem imprimir `Error loading src/payload.config.ts`. +- [x] Um export morto plantado em `src/lib/campaignTime.ts` é detectado (`Unused exports`), provando análise completa. +- [x] `payload-types.ts` não gera findings espúrios (`!src/payload-types.ts` presente, a exclusão load-bearing). +- [x] Bloco `paths.server-only` removido; `importMap.js` segue como `entry` explícito e commitado. +- [x] `docs/TECH-DEBT.md:10` fechado, referências P3 obsoletas corrigidas, changelog `docs/changelog/2026-09-16-ops124.md` criado. +- [ ] `pnpm gate:fast` e `pnpm push` verdes (gate:fast verde; push no fechamento). + +## Self-score (decisão) + +5/5 — (1) decisões caras com rejeição explícita (B/C/D com motivo e prova); (2) cabe no appetite de ~30 min sem tocar schema/deploy/CI; (3) rabbit holes nomeados (CI order, `knip:production`, demais pontos do `knip.json`, status do importMap); (4) profundidade reusa o existente — corrige o `knip.json` dono da decisão e reaproveita as contribuições do plugin em vez de criar caminho paralelo; (5) o outcome da intenção é satisfeito com prova objetiva (ERROR some + export morto detectado + controle de zero perda). diff --git a/docs/plans/ops-knip-importmap-artifact.md b/docs/plans/ops-knip-importmap-artifact.md index 547d60af..c8a98adf 100644 --- a/docs/plans/ops-knip-importmap-artifact.md +++ b/docs/plans/ops-knip-importmap-artifact.md @@ -1,8 +1,8 @@ # OPS: knip carrega o payload.config.ts com erro (importMap commitado) e roda com análise degradada -Status: rascunho +Status: executado (impl: docs/plans/ops-knip-importmap-artifact-impl.md) Atualizado em: 2026-09-16 -Issue: a registrar — OPS124 +Issue: #1094 (OPS124) Priority: P3 Impeccable: A — sem UI Appetite: ~30 min eng; sem schema, sem deploy @@ -11,7 +11,9 @@ Appetite: ~30 min eng; sem schema, sem deploy `pnpm knip` imprime `ERROR: Error loading src/payload.config.ts (This module cannot be imported from a Client Component module. It should only be used from a Server Component.)` e **sai com código 0** — acontece no CI (run `35099887216`) e local, sempre que `src/app/(payload)/admin/importMap.js` está no working tree. Esse arquivo está **commitado** (apesar de `knip.json`/OPS99/`.gitignore` o tratarem como artefato de build), importa componentes client e faz o knip carregar `payload.config.ts`; o `server-only` resolve para o entry que lança sob as condições padrão (não `react-server`), e a regra `paths.server-only` do `knip.json` não está sendo aplicada ao specifier de pacote. Sem o arquivo, o knip completa (com 2 unresolved imports esperados de `[[...segments]]`). Por sair 0, o gate fica verde com o knip **degradado em silêncio**: um ERROR que ninguém lê e uma análise possivelmente incompleta (o entry falhou ao carregar). -Correção de rumo (2026-09-16): durante o C178 eu supus que isso quebrava `pnpm push`/`gate:ci` local; na prática o exit é 0 e o push não é afetado. O problema real é o erro silencioso/análise degradada. +Correção de rumo (2026-09-16): durante o C178 eu supus que isso quebrava `pnpm push`/`gate:ci` local; na prática o exit é 0 e o push não é afetado. O problema real é o erro silencioso. + +Segunda correção de rumo (2026-09-16, no impl): o ERROR é **independente** do `importMap.js` — ele reaparece só de carregar a config, com ou sem o arquivo; o `importMap` commitado apenas troca o exit 1 (unresolved `../importMap`) por um exit 0. E o `paths.server-only` **não** pode corrigir o carregamento (só alimenta o resolver estático do knip). O caminho escolhido é desabilitar o plugin do Payload e explicitar o que ele contribuía — ver `docs/plans/ops-knip-importmap-artifact-impl.md`. ## Fases verificáveis diff --git a/knip.json b/knip.json index 70137ec2..bcc7f4ee 100644 --- a/knip.json +++ b/knip.json @@ -9,15 +9,18 @@ "tests/**/*.spec.ts", "tests/helpers/serverOnly.ts" ], - "project": ["src/**/*.{ts,tsx,js}", "scripts/**/*.mjs", "tests/**/*.ts"], + "project": [ + "src/**/*.{ts,tsx,js}", + "scripts/**/*.mjs", + "tests/**/*.ts", + "!src/migrations/**", + "!src/payload-types.ts" + ], "ignore": [ "src/components/ui/bubble.tsx", "src/components/ui/marker.tsx", "src/components/ui/message.tsx" ], - "paths": { - "server-only": ["tests/helpers/serverOnly.ts"] - }, "ignoreBinaries": ["opencode", "ffmpeg", "ffprobe"], "ignoreDependencies": [ "tailwindcss-animate", @@ -31,5 +34,6 @@ "types": "error", "enumMembers": "error", "duplicates": "off" - } + }, + "payload": false }