diff --git a/.claude/agents/cnab240-bradesco.md b/.claude/agents/cnab240-bradesco.md index c6179ab4b..862a04bd9 100644 --- a/.claude/agents/cnab240-bradesco.md +++ b/.claude/agents/cnab240-bradesco.md @@ -155,8 +155,8 @@ bonito. Fora do escopo atual, use a skill [`issue-report`](../skills/issue-repor PDF. - **Fatiar arquivo `.REM` por Bash é o caminho certo.** Não é código do repositório. Escreva o script no scratchpad da sessão, nunca dentro do repo. -- **`pnpm`, jamais `npm`** — hook recusa. Scripts utilitários em TypeScript (`node - --experimental-strip-types`), nunca Python. +- **`pnpm`, jamais `npm`** — hook recusa. Scripts utilitários em TypeScript, executados com + `node arquivo.ts` (sem flag), nunca Python. - **Gate antes de fechar:** `pnpm run typecheck && format:check && lint && test`. Vermelho é regressão a corrigir agora, tenha ou não vindo do seu diff — nunca `skip`, nunca hook desligado. Se travar, use a skill [`ts-quality-checker`](../skills/ts-quality-checker/SKILL.md). diff --git a/.claude/agents/docker-compose-expert.md b/.claude/agents/docker-compose-expert.md index f9faa76cd..7ea7f2704 100644 --- a/.claude/agents/docker-compose-expert.md +++ b/.claude/agents/docker-compose-expert.md @@ -75,7 +75,8 @@ Agente especialista em **Docker / Docker Compose** para o `core-api`. Atua quand ## Constraints invariantes - **Imagem base Node** (quando ativada a build de prod do app): `node:24-bookworm-slim` (ou `node:24-alpine` se aceitarmos musl trade-off — ADR pendente). Sem `latest`. Sem `node:24` sem o `-slim`/`-alpine`. -- **Multi-stage obrigatório** em prod: `deps` → `build` (se tiver compile step; hoje `--experimental-strip-types`, então build step opcional) → `runtime`. +- **`# syntax=docker/dockerfile:1`, nunca um minor fixo.** A doc oficial ([build/buildkit/frontend](https://docs.docker.com/build/buildkit/frontend/)) diz que `:1` acompanha a última `1.x.x` e que uma versão específica "needs to be updated manually to continue receiving bugfixes". Minor fixo não é imutável (recebe patches `x.y.z`) nem atualizado — este repositório ficou em `1.10` por 17 minors. O digest pin do ADR-0011 vale para a imagem **base**, que vai para o runtime; o frontend do `# syntax` é baixado em build e não entra na imagem. +- **Multi-stage obrigatório** em prod: `deps` → `build` (se tiver compile step; hoje o Node executa `.ts` direto, então build step opcional) → `runtime`. - **`USER nonroot`** (UID >= 10000) no estágio final. - **`.dockerignore` rico** — `node_modules`, `.git`, `dist`, `coverage`, `.env*`, `secrets/`, `*.log`, `.claude/`, `handbook/`. - **`HEALTHCHECK` declarado** em serviços críticos (MySQL, MinIO). @@ -166,7 +167,7 @@ secrets: ### `Dockerfile` (esqueleto para quando build de prod for ativada — hoje a CLI roda local) ```Dockerfile -# syntax=docker/dockerfile:1.7 +# syntax=docker/dockerfile:1 ARG NODE_VERSION=24-bookworm-slim # --- deps ------------------------------------------------------------------ @@ -198,10 +199,11 @@ COPY --chown=app:app package.json ./ USER app EXPOSE 3000 HEALTHCHECK --interval=10s --timeout=3s --retries=3 \ - CMD node --experimental-strip-types --no-warnings -e "fetch('http://localhost:3000/health').then(r => r.ok ? 0 : process.exit(1)).catch(() => process.exit(1))" + CMD node -e "fetch('http://localhost:3000/health').then(r => r.ok ? 0 : process.exit(1)).catch(() => process.exit(1))" # `tini` (PID 1 init) — opcional via flag init: true do compose, ou via ENTRYPOINT. -ENTRYPOINT ["node", "--experimental-strip-types", "--no-warnings"] +# Sem flag de runtime: o type stripping é default no Node 24. +ENTRYPOINT ["node"] CMD ["src/server.ts"] ``` diff --git a/.claude/agents/nodejs-runtime-expert.md b/.claude/agents/nodejs-runtime-expert.md index 439fed9dd..228f43d8e 100644 --- a/.claude/agents/nodejs-runtime-expert.md +++ b/.claude/agents/nodejs-runtime-expert.md @@ -25,7 +25,7 @@ description: > # nodejs-runtime-expert -Agente especialista em **Node.js 24 LTS** com **ESM + NodeNext + `--experimental-strip-types`** para o `core-api`. Atua como engenheiro sênior do runtime — escolhe API nativa antes de dep externa, modela graceful shutdown, diagnostica erros de module resolution. +Agente especialista em **Node.js 24 LTS** com **ESM + NodeNext + type stripping nativo** (`node arquivo.ts`, sem flag) para o `core-api`. Atua como engenheiro sênior do runtime — escolhe API nativa antes de dep externa, modela graceful shutdown, diagnostica erros de module resolution. > **Herda integralmente** o `CLAUDE.md` raiz, [ADR-0002](../../handbook/architecture/adr/0002-keep-nodejs-runtime.md) (Node como runtime), [ADR-0009](../../handbook/architecture/adr/0009-node-24-typescript-6-with-7-roadmap.md) (Node 24 + TS 6). Roteador único: [`contratos-orchestrator`](./contratos-orchestrator.md). @@ -36,13 +36,17 @@ Agente especialista em **Node.js 24 LTS** com **ESM + NodeNext + `--experimental | Tecnologia | Versão / Flag | Origem | | :----------------------------- | :------------------------------------------------- | :------------------------------- | | Node.js | `>=24.0.0` | `package.json#engines.node` | -| `@types/node` | `^22.10.0` | `package.json#devDependencies` | -| TS execution mode | `--experimental-strip-types --no-warnings` | scripts `test`, `test:integration`, `cli:contracts`, `secrets:setup` | +| `@types/node` | `^24.0.0` | `package.json#devDependencies` | +| TS execution mode | `node arquivo.ts` — **sem flag** | todos os scripts do `package.json` | | Module system | ESM (`"type": "module"`) | `package.json#type` | | `tsconfig.module` | `NodeNext` + `allowImportingTsExtensions` | `tsconfig.json` | | Subpath imports | `#src/*` → `./src/*` | `package.json#imports` | -`--experimental-strip-types` está em "Active development" em Node 24 (não Stable). Toda mudança que dependa dele acima de "test/dev" tem que ser validada — registrar em ADR se virar dependência de prod. +O type stripping **não é mais experimental**: é o comportamento default do Node desde a 23.6 (backport na 22.18), e a prova verificável é `--no-strip-types` existir como negação no `node --help`. Produção depende dele — o `ENTRYPOINT` da imagem é `tini -- node src/server.ts`, sem flag. + +⚠️ **Nunca escrever `--experimental-strip-types` nem `--no-warnings` num comando deste repositório.** As duas foram removidas em 06/10/2026 após medição: a primeira é redundante; a segunda **esconde** `DeprecationWarning`, que é como o Node anuncia o que quebra na próxima major. Cobrado por `tests/cleanup/node-flags-not-redundant.test.ts` em todos os pontos de invocação. O que exige flag é `enum`/`namespace` (`--experimental-transform-types`) — e o repositório não tem nenhum, por `erasableSyntaxOnly` no `tsconfig`. + +⚠️ **`--enable-source-maps` é o contrário: ela FICA, e removê-la é regressão.** A primeira versão desta regra mandava tirá-la, com a justificativa de que nenhuma dep de produção publicava `.js.map`. Era falso por erro de medição — o `find` rodou **sem `-L`**, e `node_modules/` é symlink sob pnpm, então a varredura nunca desceu no alvo. Com `find -L`: `drizzle-orm` publica **444** `.js.map`, `fast-xml-parser` mais 4. Reproduzido: sem a flag, um erro dentro da lib reporta `drizzle-orm/utils.js:109`; com ela, `src/utils.ts:208`. O type stripping preserva a posição do código **deste** repositório — e é por isso que o raciocínio engana: a flag nunca serviu a ele, serve às deps que publicam mapa. **Ao medir qualquer coisa dentro de `node_modules` sob pnpm, use `find -L`.** --- @@ -170,9 +174,9 @@ describe('Money.fromCents', () => { Rodar suíte específica: ```bash -node --test --experimental-strip-types --no-warnings tests/modules/contracts/domain/shared/money.test.ts +node --test tests/modules/contracts/domain/shared/money.test.ts # Por nome: -node --test --experimental-strip-types --no-warnings --test-name-pattern="fromCents" tests/.../money.test.ts +node --test --test-name-pattern="fromCents" tests/.../money.test.ts ``` ### AsyncLocalStorage para correlação diff --git a/.claude/agents/typescript-language-expert.md b/.claude/agents/typescript-language-expert.md index 190399ca2..3d369d813 100644 --- a/.claude/agents/typescript-language-expert.md +++ b/.claude/agents/typescript-language-expert.md @@ -23,7 +23,7 @@ description: > # typescript-language-expert -Agente especialista em **TypeScript 6.0** (com roadmap para TS 7 via `@typescript/native-preview` — [ADR-0009](../../handbook/architecture/adr/0009-node-24-typescript-6-with-7-roadmap.md)) para o repositório `core-api`. Atua como engenheiro sênior do type system — modela tipos avançados, justifica narrowing, lê o handbook oficial antes de prescrever. +Agente especialista em **TypeScript 7** (compilador nativo em Go — [ADR-0067](../../handbook/architecture/adr/0067-typescript-7-side-by-side-supersedes-0009-language.md) D1, sem o side-by-side desde o [ADR-0072](../../handbook/architecture/adr/0072-oxc-lint-supersedes-inquiry-0029-and-0067-d2.md)) para o repositório `core-api`. Atua como engenheiro sênior do type system — modela tipos avançados, justifica narrowing, lê o handbook oficial antes de prescrever. > **Herda integralmente** o `CLAUDE.md` raiz (especialmente §"Anti-padrões"). Toda mudança em código de produção fecha no gate: `typecheck` + `format:check` + `lint` + `test`. @@ -33,9 +33,8 @@ Agente especialista em **TypeScript 6.0** (com roadmap para TS 7 via `@typescrip | Pacote | Versão | Origem | | :------------------------------ | :--------------------------- | :------------------------------ | -| `typescript` | `^6.0.0` | `package.json#devDependencies` | -| `@typescript/native-preview` | `7.0.0-dev.20260515.1` | `package.json#devDependencies` ([ADR-0009](../../handbook/architecture/adr/0009-node-24-typescript-6-with-7-roadmap.md) §"Plano de migração") | -| `typescript-eslint` | `^8.59.3` | typecheck-aware lint | +| `typescript` | `7.0.2` (exata) | `package.json#devDependencies` — exata porque os gates de AST usam a API `unstable/` | +| `oxlint` + `oxlint-tsgolint` | `1.86.0` + `7.0.2003` | lint type-aware (`.oxlintrc.json`) | `tsconfig.json` aplica strict completo: `strict`, `noUncheckedIndexedAccess`, `noImplicitOverride`, `noFallthroughCasesInSwitch`, `noImplicitReturns`, `exactOptionalPropertyTypes`, `useUnknownInCatchVariables`, `isolatedModules`, `verbatimModuleSyntax`, `allowImportingTsExtensions`, `module: NodeNext`. @@ -80,7 +79,7 @@ Agente especialista em **TypeScript 6.0** (com roadmap para TS 7 via `@typescrip - .claude/skills/ts-quality-checker/SKILL.md ← gate de qualidade ``` -ADR-0009 é vinculante para qualquer decisão de runtime TS / TS 7 / `native-preview`. +ADR-0067 (D1, D3–D6) e ADR-0072 são vinculantes para qualquer decisão de compilador e de lint; o ADR-0009 segue vinculante no que eles não supersedem (runtime, `tsconfig` strict). --- @@ -128,7 +127,7 @@ Re-leia o §"Regras invariantes de código" do `CLAUDE.md`. Resumo: - **Branded types** para IDs e valores validados (smart constructor `(raw) => Result`). - **Discriminated union + `switch` exaustivo** — usar `const _: never = x` no default em vez de `throw`. - **`unknown` + narrowing** em vez de `any`. Se `as` for inevitável, comentar (`as unknown as T` em borda de adapter). -- **Return type explícito em funções exportadas** (regra do projeto via ESLint). +- **Return type explícito em funções exportadas** (regra do projeto via `explicit-function-return-type` no oxlint). - **Subpath imports `#src/*`** em testes para evitar `../../../../`. ### Nunca @@ -236,7 +235,7 @@ const REGISTRY = { 2. **Buscar a página do Handbook correspondente** em `handbook/reference/typescript/`. 3. **Propor 2 versões** — mínima (que resolve o problema) e ergonômica (que cria affordance para quem chama). Decidir com o usuário. 4. **Verificar com `pnpm typecheck`** — invariante. -5. **Verificar com ESLint** (`typescript-eslint` strict + type-checked). +5. **Verificar com `pnpm run lint`** (oxlint type-aware) e o gate `tests/cleanup/lint-gaps.test.ts` (casing e `class`). 6. **Documentar limitações** (perda de brand em transformação aninhada, etc.) em comentário no `.ts`. --- diff --git a/.claude/hooks/block-inline-interpreter.sh b/.claude/hooks/block-inline-interpreter.sh index 9af0e84aa..f9d187409 100755 --- a/.claude/hooks/block-inline-interpreter.sh +++ b/.claude/hooks/block-inline-interpreter.sh @@ -88,7 +88,7 @@ Substituições: reescrever arquivo inteiro → Write ler parte de arquivo → Read (offset/limit) buscar padrão → Grep - transformar texto em lote → script .ts com node --experimental-strip-types + transformar texto em lote → script .ts executado com `node arquivo.ts` (é o runtime do projeto — ver scripts/) Se precisar mesmo de processamento pontual em shell, `jq`, `grep`, `sed` e `awk` diff --git a/.claude/hooks/pre-commit-tombstone.sh b/.claude/hooks/pre-commit-tombstone.sh index 258c965db..e3ab42ec9 100755 --- a/.claude/hooks/pre-commit-tombstone.sh +++ b/.claude/hooks/pre-commit-tombstone.sh @@ -31,5 +31,5 @@ if [ -z "${NODE_BIN}" ]; then exit 1 fi -exec "${NODE_BIN}" --experimental-strip-types --no-warnings \ +exec "${NODE_BIN}" \ "${REPO_ROOT}/scripts/handbook/tombstone.ts" diff --git a/.claude/hooks/pre-commit-typecheck.sh b/.claude/hooks/pre-commit-typecheck.sh index 0420c1daa..9f7001aff 100755 --- a/.claude/hooks/pre-commit-typecheck.sh +++ b/.claude/hooks/pre-commit-typecheck.sh @@ -89,19 +89,19 @@ echo "▶ [2/4] Type check (tsc --noEmit)..." >&2 run_pnpm_script "typecheck" "typecheck" # --------------------------------------------------------------------------- -# Check 3 — Lint (ESLint + typescript-eslint) +# Check 3 — Lint (oxlint, type-aware via oxlint-tsgolint) # --------------------------------------------------------------------------- echo "" >&2 -echo "▶ [3/4] Lint (eslint)..." >&2 +echo "▶ [3/4] Lint (oxlint)..." >&2 run_pnpm_script "lint" "lint" # --------------------------------------------------------------------------- -# Check 4 — Tests (node:test + --experimental-strip-types) +# Check 4 — Tests (node:test; o stripping de tipos é default no Node 24, sem flag) # --------------------------------------------------------------------------- HAS_TESTS=$(find "${CORE_API_DIR}/tests" -name '*.test.ts' -print -quit 2>/dev/null || true) if [ -n "${HAS_TESTS}" ]; then echo "" >&2 - echo "▶ [4/4] Test run (node --test --experimental-strip-types)..." >&2 + echo "▶ [4/4] Test run (node --test)..." >&2 run_pnpm_script "test" "tests" else echo "" >&2 diff --git a/.claude/hooks/regen-inquiry-index.sh b/.claude/hooks/regen-inquiry-index.sh index ccaa46923..af7162f57 100755 --- a/.claude/hooks/regen-inquiry-index.sh +++ b/.claude/hooks/regen-inquiry-index.sh @@ -25,6 +25,6 @@ case "$file" in esac cd "${CLAUDE_PROJECT_DIR:-.}" || exit 0 -node --experimental-strip-types --no-warnings scripts/handbook/inquiry-index.ts >/dev/null 2>&1 +node scripts/handbook/inquiry-index.ts >/dev/null 2>&1 exit 0 diff --git a/.claude/hooks/stop-quality-gate.sh b/.claude/hooks/stop-quality-gate.sh index 4c41488b2..68940312b 100755 --- a/.claude/hooks/stop-quality-gate.sh +++ b/.claude/hooks/stop-quality-gate.sh @@ -11,7 +11,7 @@ # - nenhum arquivo alterado → exit 0 imediato # - só .md / .yaml / .sh / qualquer não-.ts → exit 0 (format já roda no PostToolUse) # - QUALQUER .ts, em qualquer diretório → gate completo -# - tsconfig.json, eslint.config.js, package.json → gate completo +# - tsconfig.json, .oxlintrc.json, package.json → gate completo # - .claude/rules/*.md → gate completo, apesar de ser .md: o bloco # `verify:` dessas rules AFIRMA coisas sobre o código, e quem confere essas afirmações é # `tests/cleanup/rules-self-verify.test.ts` — que só roda dentro de `pnpm test`. Sem esta @@ -60,7 +60,7 @@ while IFS= read -r f; do # `src/*.ts|tests/*.ts|…` que existiam aqui eram código morto, subsumidas por ele. *.ts) needs_gate=1; break ;; .claude/rules/*.md) needs_gate=1; break ;; - tsconfig.json | eslint.config.js | package.json) needs_gate=1; break ;; + tsconfig.json | .oxlintrc.json | package.json) needs_gate=1; break ;; esac done <<< "$changed" @@ -68,7 +68,7 @@ log "arquivos alterados: ${n_changed}" if [ "$needs_gate" -eq 0 ]; then log "veredito: PULADO — nenhum alterado aciona o gate (sem .ts, tsconfig.json," - log " eslint.config.js ou package.json). Formatação já rodou no PostToolUse." + log " .oxlintrc.json ou package.json). Formatação já rodou no PostToolUse." log "" log "alterados:" printf '%s\n' "$changed" | sed 's/^/ /' >> "$LOGFILE" diff --git a/.claude/rules/adapters.md b/.claude/rules/adapters.md index e2f9f31ec..93ff8821f 100644 --- a/.claude/rules/adapters.md +++ b/.claude/rules/adapters.md @@ -26,7 +26,7 @@ verify: - 'src/modules/financial/adapters/persistence/repos/document-repository.drizzle.ts' --- -Única camada que toca infra real (Drizzle, mysql2, S3, FS, processo externo). `ENUM` e JSON nativos são barrados por [`.semgrep/rules.yml`](../../.semgrep/rules.yml); `class` por ESLint; o prefixo de tabela por `tests/cleanup/table-prefix-isolation.test.ts`; o pool boot-scoped está em [`shared-persistence.md`](./shared-persistence.md). Nada disso se repete aqui. +Única camada que toca infra real (Drizzle, mysql2, S3, FS, processo externo). `ENUM` e JSON nativos são barrados por [`.semgrep/rules.yml`](../../.semgrep/rules.yml); `class` por `tests/cleanup/lint-gaps.test.ts`; o prefixo de tabela por `tests/cleanup/table-prefix-isolation.test.ts`; o pool boot-scoped está em [`shared-persistence.md`](./shared-persistence.md). Nada disso se repete aqui. - **A borda do adapter converte exceção em `Result` — e é a única que pode.** `try/catch` é permitido aqui e em lugar nenhum acima: nem `Error`, nem exception, nem `null` cru sobem para application ou domain. Vale também para os mappers row↔domínio, que retornam `Result` porque o banco pode conter estado que o domínio rejeita. Uma exceção que vaza daqui destrói a propriedade que o `Result` existe para dar: erro visível na assinatura. diff --git a/.claude/rules/application.md b/.claude/rules/application.md index de626f5fd..5b5b9a920 100644 --- a/.claude/rules/application.md +++ b/.claude/rules/application.md @@ -9,7 +9,7 @@ verify: expect: [] --- -Application orquestra: não tem regra de negócio nem conhece infra. A regra de dependência (não importar `adapters/`) e a proibição de `interface` em port são cobradas por `tests/cleanup/application-depends-inward.test.ts`; `class` é barrado globalmente por ESLint. A atomicidade do `INSERT` na outbox vive em [`adapters.md`](./adapters.md), porque `appendOutboxInTx` é chamado em 12 arquivos de `adapters/` e em **nenhum** de `application/`. +Application orquestra: não tem regra de negócio nem conhece infra. A regra de dependência (não importar `adapters/`) e a proibição de `interface` em port são cobradas por `tests/cleanup/application-depends-inward.test.ts`; `class` é barrado globalmente por `tests/cleanup/lint-gaps.test.ts`. A atomicidade do `INSERT` na outbox vive em [`adapters.md`](./adapters.md), porque `appendOutboxInTx` é chamado em 12 arquivos de `adapters/` e em **nenhum** de `application/`. - **Use case é factory function que recebe `Deps` e devolve `Result`.** `(deps: Readonly<{…}>) => (input) => Promise>` — 153 dos 157 use cases declaram um tipo `*Deps`, 151 devolvem `Promise>`. A sequência dentro é sempre a mesma: **validar → buscar → decidir no domínio → persistir → publicar evento**. O que faz esse formato valer não é a estética: é que `deps` ser argumento torna o teste uma chamada de função, sem container nem mock de módulo. diff --git a/.claude/rules/domain.md b/.claude/rules/domain.md index d7705163a..3171e99ec 100644 --- a/.claude/rules/domain.md +++ b/.claude/rules/domain.md @@ -14,7 +14,7 @@ verify: Domínio puro: zero infra, zero framework, zero I/O. Abrange `src/modules/*/domain/` e o shared kernel (`src/shared/kernel/` — `Money`, `NonZeroMoney`, `Period`, `PlainDate`, `UserRef`, `Cpf`, `Cnpj`). -O que já é cobrado por mecanismo e **não** se repete aqui: `throw` (`tests/cleanup/domain-no-throw.test.ts`), `class`/`this` (ESLint `no-restricted-syntax`), `any` (`no-explicit-any`), `switch` exaustivo (`switch-exhaustiveness-check` + `noFallthroughCasesInSwitch`) e parâmetro mutável (`prefer-readonly-parameter-types`). As primitivas `Result`, `Brand`, `immutable` e `exhaustiveStringUnion` vivem em `src/shared/primitives/` e têm rule própria — [`shared-primitives.md`](./shared-primitives.md). +O que já é cobrado por mecanismo e **não** se repete aqui: `throw` (`tests/cleanup/domain-no-throw.test.ts`), `class` (`tests/cleanup/lint-gaps.test.ts`), `any` (`no-explicit-any`), `switch` exaustivo (`switch-exhaustiveness-check` + `noFallthroughCasesInSwitch`) e parâmetro mutável (`prefer-readonly-parameter-types`). As primitivas `Result`, `Brand`, `immutable` e `exhaustiveStringUnion` vivem em `src/shared/primitives/` e têm rule própria — [`shared-primitives.md`](./shared-primitives.md). - **Erro de domínio é string literal union, nunca classe.** `type ContractError = 'contract-not-active' | 'contract-cannot-expire-yet' | …`. É o que faz o `E` do `Result` ser exaustivo no `switch` do chamador — e o que o compilador consegue cobrar. Uma classe de erro devolve a decisão para runtime e reintroduz hierarquia onde o projeto escolheu união fechada. diff --git a/.claude/rules/http-edge.md b/.claude/rules/http-edge.md index dd7bdc6b2..044caf1a3 100644 --- a/.claude/rules/http-edge.md +++ b/.claude/rules/http-edge.md @@ -12,7 +12,7 @@ verify: - 'src/server.ts' --- -A borda HTTP é a **UX primária** ([ADR-0037](../../handbook/architecture/adr/0037-http-first-retire-embedded-cli.md)). A fronteira do Zod e o isolamento do shell transversal são cobrados por `tests/cleanup/http-edge-boundary.test.ts`; o glob de borda no `eslint.config.js` apenas **afrouxa** regras para os tipos do Fastify — não impõe nada. +A borda HTTP é a **UX primária** ([ADR-0037](../../handbook/architecture/adr/0037-http-first-retire-embedded-cli.md)). A fronteira do Zod e o isolamento do shell transversal são cobrados por `tests/cleanup/http-edge-boundary.test.ts`; o override de borda no `.oxlintrc.json` apenas **afrouxa** regras para os tipos do Fastify — não impõe nada. ## Onde cada coisa mora ([ADR-0028](../../handbook/architecture/adr/0028-http-edge-shell-location.md)) diff --git a/.claude/rules/supply-chain.md b/.claude/rules/supply-chain.md index fd64eb8a3..db55e7995 100644 --- a/.claude/rules/supply-chain.md +++ b/.claude/rules/supply-chain.md @@ -16,7 +16,7 @@ verify: Estes arquivos carregam **decisão de segurança**, não configuração incidental. A política nasceu de um incidente real: o comprometimento do `axios` em março/2026 ([ADR-0011](../../handbook/architecture/adr/0011-supply-chain-hardening.md)). -As quatro settings de quarentena e a concordância de versão do pnpm entre `packageManager`, `engines.pnpm` e `ENV PNPM_VERSION` são cobradas por `tests/cleanup/supply-chain-settings.test.ts`. A lista de libs banidas do ADR-0011 §4 é enforced por `@typescript-eslint/no-restricted-imports`, com a mensagem do ADR dentro do erro. Nenhuma das duas se repete aqui. +As quatro settings de quarentena e a concordância de versão do pnpm entre `packageManager`, `engines.pnpm` e `ENV PNPM_VERSION` são cobradas por `tests/cleanup/supply-chain-settings.test.ts`. A lista de libs banidas do ADR-0011 §4 é enforced por `no-restricted-imports` no `.oxlintrc.json`, com a mensagem do ADR dentro do erro. Nenhuma das duas se repete aqui. > ⚠️ **Cite [ADR-0029](../../handbook/architecture/adr/0029-pnpm-11-supply-chain-defaults.md), não o ADR-0012.** O 0012 está **Superseded** desde 2026-05-30 — a escolha do pnpm continua válida, mas a norma vigente (major 11 + defaults) é o 0029. diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md index 4f3d9041b..409d6db76 100644 --- a/.claude/rules/testing.md +++ b/.claude/rules/testing.md @@ -15,7 +15,7 @@ verify: expect: ['scripts/handbook/link-scan.ts'] --- -Runner: Node test runner nativo + `--experimental-strip-types`. O glob de descoberta é **um só** — `tests/**/*.test.ts` — e `tests/cleanup/test-discovery.test.ts` garante que nada de teste caia fora dele. As regras ESLint relaxadas aqui (`floating-promises`, `non-null-assertion`, `return-type`, `naming-convention`) estão em `eslint.config.js`, no bloco `files: ['tests/**/*.ts']`. +Runner: Node test runner nativo, executando `.ts` direto. O type stripping é default desde o Node 23.6, e `--experimental-strip-types`/`--no-warnings` foram removidos de todos os pontos de invocação do repositório — `tests/cleanup/node-flags-not-redundant.test.ts` cobra cada um e impede o retorno. **`--enable-source-maps` permanece**, e não por inércia: `drizzle-orm` publica 444 `.js.map`, e sem a flag um erro dentro da lib reporta o `.js` empacotado em vez do `.ts` de origem. O glob de descoberta é **um só** — `tests/**/*.test.ts` — e `tests/cleanup/test-discovery.test.ts` garante que nada de teste caia fora dele. As regras de lint relaxadas aqui (`floating-promises`, `non-null-assertion`, `return-type`) estão no `.oxlintrc.json`, no override `files: ['tests/**/*.ts']`; o `naming-convention`, que o oxlint não tem, fica de fora de `tests/` no próprio gate `tests/cleanup/lint-gaps.test.ts`. ## Quatro naturezas de arquivo, e só uma roda no gate @@ -49,7 +49,7 @@ Suítes de integração (`*.drizzle*.test.ts`, `*.integration.test.ts`) rodam ** sed -n '/^ partners: mysqlSuite(/,/^ ]),/p' scripts/ci/test-integration.ts \ | grep -oE "'tests/[^']+'" | tr -d "'" \ | awk '{a[NR]=$0} END{for(i=NR;i>0;i--) print a[i]}' \ - | xargs node --test --test-concurrency=1 --experimental-strip-types + | xargs node --test --test-concurrency=1 ``` A inversão usa `awk` de propósito: a versão anterior desta rule usava `tail -r`, que **só existe no macOS** — no CI Linux o comando falhava antes de rodar teste algum. diff --git a/.claude/skills/clean-code-reviewer/SKILL.md b/.claude/skills/clean-code-reviewer/SKILL.md index 47aea3d2d..a1f85bc5f 100644 --- a/.claude/skills/clean-code-reviewer/SKILL.md +++ b/.claude/skills/clean-code-reviewer/SKILL.md @@ -75,7 +75,7 @@ Ver [`modules/anti-padroes-locais.md`](modules/anti-padroes-locais.md). | Tópico | Onde olhar | | :--- | :--- | | Regras invariantes locais (zero `throw`/`class`/`any`, branded types, Result, switch exaustivo) — leia ANTES da review | [`../../../CLAUDE.md`](../../../CLAUDE.md) §"Regras invariantes" e §"Anti-padrões" | -| Enforcement automatizado (typescript-eslint strict + type-checked + stylistic) | [`../../../eslint.config.js`](../../../eslint.config.js) — destacar: `no-restricted-syntax` (banimento de class), `switch-exhaustiveness-check`, `strict-boolean-expressions`, `prefer-readonly-parameter-types`, `consistent-type-imports`, `naming-convention`, `max-params: 4` | +| Enforcement automatizado (oxlint type-aware, paridade com o antigo typescript-eslint strict + stylistic) | [`../../../.oxlintrc.json`](../../../.oxlintrc.json) — destacar: `switch-exhaustiveness-check`, `strict-boolean-expressions`, `prefer-readonly-parameter-types`, `consistent-type-imports`, `max-params: 4`; banimento de `class` e `naming-convention` em [`../../../tests/cleanup/lint-gaps.test.ts`](../../../tests/cleanup/lint-gaps.test.ts) | | Formatador (não revisar estilo manual — Prettier faz) | [`../../../.prettierrc.json`](../../../.prettierrc.json), [`../../../.prettierignore`](../../../.prettierignore) | | Skill irmã na review específica do core-api | [`../code-reviewer/SKILL.md`](../code-reviewer/SKILL.md) | | Skill irmã para reviewer de DB | [`../database-engineer/SKILL.md`](../database-engineer/SKILL.md) | diff --git a/.claude/skills/clean-code-theorist/SKILL.md b/.claude/skills/clean-code-theorist/SKILL.md index 91e7d74e7..b8a4c8abb 100644 --- a/.claude/skills/clean-code-theorist/SKILL.md +++ b/.claude/skills/clean-code-theorist/SKILL.md @@ -76,15 +76,15 @@ Quando comparar escolas, este projeto oferece evidência empírica de cada escol | Debate | Posição adotada aqui | Evidência | | :--- | :--- | :--- | -| **Uncle Bob (OO clássico) vs Functional Programming pura** | FP pragmática vence: zero `class`, zero `this`, tudo `Readonly<>` + funções standalone. Banido por ESLint `no-restricted-syntax` | [`../../../eslint.config.js`](../../../eslint.config.js); [`../../../CLAUDE.md`](../../../CLAUDE.md) §"Regras invariantes" | +| **Uncle Bob (OO clássico) vs Functional Programming pura** | FP pragmática vence: zero `class`, zero `this`, tudo `Readonly<>` + funções standalone. `class` é banida pelo gate de AST | [`../../../tests/cleanup/lint-gaps.test.ts`](../../../tests/cleanup/lint-gaps.test.ts); [`../../../CLAUDE.md`](../../../CLAUDE.md) §"Regras invariantes" | | **Defensive programming vs Result-as-value** | Result vence — zero `throw` no domínio; erros são string literal unions tipadas | `src/shared/result.ts`, todos os `errors.ts` em `domain/` | | **DRY radical vs WET pragmático** | Durante o período dual-dialect (ADR-0018, 2026-05-14 → 2026-05-15), mappers SQLite e MySQL foram **propositalmente duplicados** em vez de unificados via genéricos TS — "tentar gerar uma única definição abstrata leva a tipos infernais". O ADR-0020 (2026-05-15) removeu o SQLite; hoje só há mappers MySQL, mas o raciocínio histórico continua válido sempre que aparecer um caso de WET pragmático: duplicação concreta < abstração inadequada. | [`ADR-0018`](../../../handbook/architecture/adr/0018-persistence-dual-dialect-drizzle.md) §"Princípio condutor" (Superseded) + [`ADR-0020`](../../../handbook/architecture/adr/0020-mysql-only-supersedes-dual-dialect.md) | | **Comments are failures (Uncle Bob) vs Comments-as-rationale (Fowler)** | Fowler vence — comentários só onde explicam **por quê**, com link para ADR/inquiry/defeito | Ver `src/modules/contracts/domain/shared/money.ts` (comenta `Defeito #8`), `period.ts` (`Defeito #7`), `create-amendment.ts` (`REGR #7`) | | **SOLID-S (Single Responsibility) interpretado como "1 motivo de mudança"** | Aplicado por separação de pastas: `domain/` muda só por regra de negócio; `adapters/` muda só por infra; `cli/` só por UX terminal | Layout em [`../../../CLAUDE.md`](../../../CLAUDE.md) §"Mapa de camadas do módulo Contracts" | -| **OO de herança vs Discriminated unions** | Discriminated unions vencem — `Amendment` é union de 4 kinds, com `switch` exaustivo (`switch-exhaustiveness-check` no ESLint) | `src/modules/contracts/domain/amendment/types.ts` | +| **OO de herança vs Discriminated unions** | Discriminated unions vencem — `Amendment` é union de 4 kinds, com `switch` exaustivo (`switch-exhaustiveness-check` no oxlint) | `src/modules/contracts/domain/amendment/types.ts` | | **GoF Factory vs Smart constructor funcional** | Smart constructor vence — `Money.fromCents(raw): Result` | `src/modules/contracts/domain/shared/money.ts`, `period.ts`, `ids.ts`, `bucket-name.ts`, `storage-key.ts` | | **Refactor sem rede vs Refactor com testes verdes (Fowler)** | Fowler vence — o gate re-roda toda a suíte, e vermelho não fecha turno | [`../../../CLAUDE.md`](../../../CLAUDE.md) §"Política de regressão zero" | -| **Lint como dogma vs Lint como apoio** | Apoio: `eslint-config-prettier` desliga regras de estilo (formatter resolve); typescript-eslint focado em segurança de tipos | [`../../../eslint.config.js`](../../../eslint.config.js) (ordem dos configs, com `prettierConfig` último) | +| **Lint como dogma vs Lint como apoio** | Apoio: o lint não liga categoria inteira de estilo (o formatter resolve); as regras portadas são as de segurança de tipos e as que traduzem decisão do projeto | [`../../../.oxlintrc.json`](../../../.oxlintrc.json) (todas as `categories` desligadas; só regras nomeadas) | --- diff --git a/.claude/skills/clean-code-tutor/SKILL.md b/.claude/skills/clean-code-tutor/SKILL.md index 8235e5014..1d42c3583 100644 --- a/.claude/skills/clean-code-tutor/SKILL.md +++ b/.claude/skills/clean-code-tutor/SKILL.md @@ -69,7 +69,7 @@ Quando o aluno disser "quero ver isso num código real", aponte: | Tópico | Onde olhar | | :--- | :--- | | Regras transversais que materializam Clean Code (naming EN, funções puras, zero side-effects no domínio) | [`../../../CLAUDE.md`](../../../CLAUDE.md) §"Regras invariantes" | -| Enforcement automatizado via ESLint flat config (typescript-eslint strict + type-checked, regras `naming-convention`, `max-params`, `prefer-readonly-parameter-types`, `consistent-type-imports`, `no-restricted-syntax` para banir `class`) | [`../../../eslint.config.js`](../../../eslint.config.js) | +| Enforcement automatizado via oxlint type-aware (regras `max-params`, `prefer-readonly-parameter-types`, `consistent-type-imports`) e gate de AST (`naming-convention`, banimento de `class`) | [`../../../.oxlintrc.json`](../../../.oxlintrc.json), [`../../../tests/cleanup/lint-gaps.test.ts`](../../../tests/cleanup/lint-gaps.test.ts) | | Formatador automático | [`../../../.prettierrc.json`](../../../.prettierrc.json) | | Skill irmã que aplica TS funcional puro | [`../ts-domain-modeler/SKILL.md`](../ts-domain-modeler/SKILL.md) — zero `class`, zero `this`, branded types, smart constructors | | Skill irmã que define ports e adapters | [`../ports-and-adapters/SKILL.md`](../ports-and-adapters/SKILL.md) — interface segregation aplicada via `type Readonly<{...}>` | diff --git a/.claude/skills/code-reviewer/SKILL.md b/.claude/skills/code-reviewer/SKILL.md index 1c91358a5..a2167aef1 100644 --- a/.claude/skills/code-reviewer/SKILL.md +++ b/.claude/skills/code-reviewer/SKILL.md @@ -33,8 +33,8 @@ Em ordem decrescente de autoridade: | Tópico | Onde olhar | | :--- | :--- | | Regras transversais (zero throw, zero class, anti-padrões numerados) | [`../../../CLAUDE.md`](../../../CLAUDE.md) §"Regras invariantes" e §"Anti-padrões" | -| ESLint flat config + `typescript-eslint` strict + stylistic + type-checked (regras automaticamente enforced) | [`../../../eslint.config.js`](../../../eslint.config.js) — incluir `no-restricted-syntax` (class), `switch-exhaustiveness-check`, `strict-boolean-expressions`, `prefer-readonly-parameter-types`, `consistent-type-imports`, `naming-convention` | -| Prettier (regras de estilo, ignorar conflitos com ESLint via `eslint-config-prettier`) | [`../../../.prettierrc.json`](../../../.prettierrc.json), [`../../../.prettierignore`](../../../.prettierignore) | +| oxlint type-aware (regras automaticamente enforced) + gate de AST para o que o oxlint não tem | [`../../../.oxlintrc.json`](../../../.oxlintrc.json) — incluir `switch-exhaustiveness-check`, `strict-boolean-expressions`, `prefer-readonly-parameter-types`, `consistent-type-imports`; `class` e `naming-convention` em [`../../../tests/cleanup/lint-gaps.test.ts`](../../../tests/cleanup/lint-gaps.test.ts) | +| Prettier (regras de estilo; o lint não liga categoria de estilo, então não há conflito) | [`../../../.prettierrc.json`](../../../.prettierrc.json), [`../../../.prettierignore`](../../../.prettierignore) | | tsconfig estrito (strict, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `verbatimModuleSyntax`, `isolatedModules`) | [`../../../tsconfig.json`](../../../tsconfig.json) | | ADRs imutáveis (vencem tudo) | [`handbook/architecture/adr/`](../../../handbook/architecture/adr/) — atenção especial a 0006, 0009, 0013, 0014, 0015, 0018, 0019 | | Reviews já realizadas (exemplos de severidade + escopo) | `tests/reports/REVIEW.md`, `tests/reports/E2E-SECURITY-REVIEW.md`, `tests/bdd/QA-REPORT.md` | diff --git a/.claude/skills/nodejs-fs-scripter/SKILL.md b/.claude/skills/nodejs-fs-scripter/SKILL.md index b3dcd4743..47d6c8d79 100644 --- a/.claude/skills/nodejs-fs-scripter/SKILL.md +++ b/.claude/skills/nodejs-fs-scripter/SKILL.md @@ -1,7 +1,7 @@ --- name: nodejs-fs-scripter description: > - Escreve scripts Node.js (TypeScript, ESM, Node 24 + --experimental-strip-types) + Escreve scripts Node.js (TypeScript, ESM, Node 24 executando .ts direto, sem flag) que substituem Bash/SH para automação de filesystem — usando node:fs/promises como API canônica. Cobre o equivalente moderno de cat, ls, cp -r, mv, rm -rf, mkdir -p, find, glob, chmod, mktemp, tail, atomic writes e watch. Trata erros @@ -42,7 +42,7 @@ Sempre a documentação oficial do Node 24 espelhada no handbook do projeto: | Tópico | Onde olhar | | :--- | :--- | | Regras transversais (zero `throw` em domínio, `import type`, extensão `.ts`, `Result`, sem `any`, sem `class`) | [`../../../CLAUDE.md`](../../../CLAUDE.md) | -| ESM + NodeNext + `--experimental-strip-types` (como invocar `.ts` direto) | [`./references/nodejs-tsx-scripts.md`](./references/nodejs-tsx-scripts.md) | +| ESM + NodeNext + type stripping nativo (como invocar `.ts` direto) | [`./references/nodejs-tsx-scripts.md`](./references/nodejs-tsx-scripts.md) | | Mapa Bash → Node (catálogo de operações comuns) | [`./references/bash-to-node-mapping.md`](./references/bash-to-node-mapping.md) | | Tratamento de `NodeJS.ErrnoException` (códigos `ENOENT`, `EEXIST`, `EACCES`, …) e tradução para `Result` ou exit code | [`./references/errno-to-result.md`](./references/errno-to-result.md) | | Padrão `Result` do projeto | [`src/shared/result.ts`](../../../src/shared/result.ts) | @@ -94,7 +94,7 @@ scripts/ # ou tools/scripts/ Execução padrão (Node 24 com strip-types, sem build step): ```bash -pnpm exec node --experimental-strip-types --no-warnings scripts/limpar-pipelines-antigos.ts --older-than 30d +pnpm exec node scripts/limpar-pipelines-antigos.ts --older-than 30d ``` Atalho recomendado em `package.json#scripts`: @@ -102,7 +102,7 @@ Atalho recomendado em `package.json#scripts`: ```json { "scripts": { - "script:clean-pipelines": "node --experimental-strip-types --no-warnings scripts/limpar-pipelines-antigos.ts" + "script:clean-pipelines": "node scripts/limpar-pipelines-antigos.ts" } } ``` @@ -112,7 +112,7 @@ Atalho recomendado em `package.json#scripts`: ## Anatomia de um script (template canônico) ```ts -#!/usr/bin/env -S node --experimental-strip-types --no-warnings +#!/usr/bin/env node // scripts/limpar-pipelines-antigos.ts // // Substitui: find .claude/tmp -mindepth 1 -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \; @@ -413,7 +413,7 @@ nodejs-fs-scripter ◄── você está aqui (scripts de manutenção, build, ## Checklist antes de declarar pronto -- [ ] Script roda com `pnpm exec node --experimental-strip-types --no-warnings scripts/.ts` sem warning de extensão. +- [ ] Script roda com `pnpm exec node scripts/.ts` sem warning de extensão. - [ ] Todos os imports relativos terminam em `.ts`; imports só de tipo usam `import type` ou `import { type X }`. - [ ] Nenhum `throw` fora da função `main()` (e mesmo lá, preferir `return code`). - [ ] Exit codes seguem sysexits.h: `0`/`1`/`64`/`65`/`66`/`74`. diff --git a/.claude/skills/nodejs-fs-scripter/references/nodejs-tsx-scripts.md b/.claude/skills/nodejs-fs-scripter/references/nodejs-tsx-scripts.md index 82052bc73..ce35d49bc 100644 --- a/.claude/skills/nodejs-fs-scripter/references/nodejs-tsx-scripts.md +++ b/.claude/skills/nodejs-fs-scripter/references/nodejs-tsx-scripts.md @@ -7,7 +7,7 @@ ## Por que rodar `.ts` direto, sem `tsc`/`esbuild`? - **Edita-roda em segundos.** Script de manutenção raramente precisa do pipeline de build. -- **Tipos no autor, JS na execução.** Node 24 com `--experimental-strip-types` **descarta** as anotações sem checar — o `tsc --noEmit` do gate é quem valida tipo. +- **Tipos no autor, JS na execução.** O Node 24 **descarta** as anotações sem checar, por padrão e sem flag — o `tsc --noEmit` do gate é quem valida tipo. - **Zero artefato.** Sem `dist/`, sem `.js` versionado, sem `.map`. `git diff` mostra a verdade. --- @@ -16,7 +16,7 @@ ```bash # direto: -pnpm exec node --experimental-strip-types --no-warnings scripts/limpar.ts +pnpm exec node scripts/limpar.ts # via script do package.json (preferido): pnpm run script:limpar @@ -29,14 +29,15 @@ chmod +x scripts/limpar.ts Shebang canônico: ```ts -#!/usr/bin/env -S node --experimental-strip-types --no-warnings +#!/usr/bin/env node ``` -`env -S` permite múltiplas flags no shebang — sem ele, só uma flag passa. +Sem flag, então sem `env -S` — ele existia só para passar múltiplas flags no shebang (um shebang +aceita um argumento só). O type stripping é default desde o Node 23.6. --- -## O que `--experimental-strip-types` faz e o que NÃO faz +## O que o type stripping faz e o que NÃO faz | ✅ Funciona | ❌ Não funciona | | :--- | :--- | diff --git a/.claude/skills/nodejs-process-runner/SKILL.md b/.claude/skills/nodejs-process-runner/SKILL.md index e8c336b5b..93a7896b6 100644 --- a/.claude/skills/nodejs-process-runner/SKILL.md +++ b/.claude/skills/nodejs-process-runner/SKILL.md @@ -2,7 +2,7 @@ name: nodejs-process-runner description: > Executa processos externos (git, docker, pnpm, mysqldump, ffmpeg, scripts shell - herdados) a partir de Node.js (TypeScript, ESM, Node 24 + --experimental-strip-types) + herdados) a partir de Node.js (TypeScript, ESM, Node 24 executando .ts direto, sem flag) usando node:child_process — sempre via execFile/spawn, nunca exec com shell. Cobre o equivalente moderno de `$(...)`, pipes, redirecionamento, sinais, timeouts, streaming de stdout/stderr e tradução de exit code → Result. Pareada com @@ -102,7 +102,7 @@ scripts/ Execução padrão: ```bash -pnpm exec node --experimental-strip-types --no-warnings scripts/snapshot-db.ts --to backups/2026-05-15.sql +pnpm exec node scripts/snapshot-db.ts --to backups/2026-05-15.sql ``` --- @@ -246,7 +246,7 @@ Precisa do shell para interpretar `|`, `>`, `*`, `$VAR`? ## Anatomia de um script (template canônico) ```ts -#!/usr/bin/env -S node --experimental-strip-types --no-warnings +#!/usr/bin/env node // scripts/git-tag-release.ts // // Substitui: git tag -a "v${VER}" -m "release ${VER}" && git push origin "v${VER}" @@ -514,7 +514,7 @@ nodejs-fs-scripter ◄──┼──► nodejs-process-runner ◄── v ## Checklist antes de declarar pronto -- [ ] Script roda com `pnpm exec node --experimental-strip-types --no-warnings scripts/.ts` sem warning. +- [ ] Script roda com `pnpm exec node scripts/.ts` sem warning. - [ ] Toda chamada externa usa `execFile`/`spawn`, **nunca** `exec` com interpolação. - [ ] `shell: false` (ou omitido — esse é o default em `execFile`/`spawn`). - [ ] Cada chamada tem `timeoutMs` ou `signal` explícito. diff --git a/.claude/skills/nodejs-process-runner/references/exec-vs-execfile-vs-spawn.md b/.claude/skills/nodejs-process-runner/references/exec-vs-execfile-vs-spawn.md index 08692aa98..50aa1df15 100644 --- a/.claude/skills/nodejs-process-runner/references/exec-vs-execfile-vs-spawn.md +++ b/.claude/skills/nodejs-process-runner/references/exec-vs-execfile-vs-spawn.md @@ -190,7 +190,7 @@ const { stdout } = await exec(`grep "${userQuery}" arquivo.txt`); // ❌❌❌ ```ts // parent.ts import { fork } from 'node:child_process'; -const worker = fork('./worker.ts', [], { execArgv: ['--experimental-strip-types'] }); +const worker = fork('./worker.ts'); // sem execArgv: o type stripping é default no Node 24 worker.send({ tipo: 'processar', items: [/* ... */] }); worker.on('message', (m) => { /* ... */ }); worker.on('close', (code) => { /* ... */ }); diff --git a/.claude/skills/tdd-strategist/SKILL.md b/.claude/skills/tdd-strategist/SKILL.md index 7b84753e7..01979376d 100644 --- a/.claude/skills/tdd-strategist/SKILL.md +++ b/.claude/skills/tdd-strategist/SKILL.md @@ -79,7 +79,7 @@ Ver [`modules/anti-padroes-locais.md`](modules/anti-padroes-locais.md). | Skill irmã no domínio puro | [`../ts-domain-modeler/SKILL.md`](../ts-domain-modeler/SKILL.md) | | Skill irmã na revisão da rede de segurança | [`../code-reviewer/SKILL.md`](../code-reviewer/SKILL.md) | | Skill irmã no gate automatizado | [`../ts-quality-checker/SKILL.md`](../ts-quality-checker/SKILL.md) | -| Runner: `node:test` nativo + `--experimental-strip-types` (sem Jest, sem Vitest, sem fast-check ainda) | [`handbook/reference/nodejs/`](../../../handbook/reference/nodejs/) | +| Runner: `node:test` nativo, `.ts` direto sem flag (sem Jest, sem Vitest, sem fast-check ainda) | [`handbook/reference/nodejs/`](../../../handbook/reference/nodejs/) | | Comandos: `pnpm test`, single-test via `--test-name-pattern` | [`../../rules/testing.md`](../../rules/testing.md) | | Estrutura mirror: `tests/` espelha `src/`. Sufixo `.test.ts` é descoberto. `.contract.ts` e `.suite.ts` são suites parametrizadas reutilizáveis (function factory que recebe `makeImpl`) | [`../../../CLAUDE.md`](../../../CLAUDE.md) §"Convenções de testes" | | Subpath imports em testes: `import { Money } from '#src/modules/contracts/domain/shared/money.ts'` | [`../../../package.json`](../../../package.json) `imports` field | diff --git a/.claude/skills/tdd-theorist/SKILL.md b/.claude/skills/tdd-theorist/SKILL.md index 60caa6574..b120ea9f8 100644 --- a/.claude/skills/tdd-theorist/SKILL.md +++ b/.claude/skills/tdd-theorist/SKILL.md @@ -81,7 +81,7 @@ Quando comparar escolas/abordagens, este projeto oferece evidência empírica de | **Cobertura vs Qualidade** | Sem threshold numérico; prova-se **cobertura por critério de aceite** | Cada critério de aceite (`- [ ]`) da issue vira um `it()`, e o caminho de erro conta como critério | | **Property-based testing** | Não adotado ainda (overhead > ganho na fase atual); reservado para `Money.add/subtract` e `Period.contains` se aparecer regressão | Caso adote: roda no mesmo `node:test` via `fast-check` (não instalado hoje) | | **Suite de contrato parametrizada** (consumida por múltiplos adapters) | Adotado — adapter InMemory e adapter real rodam o mesmo conjunto de cenários | `tests/modules/contracts/adapters/persistence/contract-repository.suite.ts`, `tests/modules/contracts/application/ports/document-storage.contract.ts` (sufixos `.suite.ts` e `.contract.ts` **não são** auto-descobertos pelo runner — são funções fábrica) | -| **Runner: `node:test` vs Jest vs Vitest** | `node:test` nativo + `--experimental-strip-types` — zero deps, zero transpiler | `package.json` `"test": "node --test --experimental-strip-types --no-warnings 'tests/**/*.test.ts'"` | +| **Runner: `node:test` vs Jest vs Vitest** | `node:test` nativo, `.ts` direto sem flag — zero deps, zero transpiler | `package.json` `"test": "node --test --test-concurrency=6 'tests/**/*.test.ts'"` | Referências cross-projeto: [`../../../CLAUDE.md`](../../../CLAUDE.md), [`handbook/reference/nodejs/`](../../../handbook/reference/nodejs/), [`../../rules/testing.md`](../../rules/testing.md) (contrato de isolamento e as quatro naturezas de arquivo de teste). diff --git a/.claude/skills/tdd-tutor/SKILL.md b/.claude/skills/tdd-tutor/SKILL.md index 203f52771..81d4fa484 100644 --- a/.claude/skills/tdd-tutor/SKILL.md +++ b/.claude/skills/tdd-tutor/SKILL.md @@ -66,7 +66,7 @@ Quando o aluno disser "quero ver TDD de verdade aqui", aponte para: | Tópico | Onde olhar | | :--- | :--- | | Vermelho não fecha turno — teste, lint, typecheck ou build | [`../../../CLAUDE.md`](../../../CLAUDE.md) §"Política de regressão zero" | -| Runner usado no projeto: `node:test` nativo + `--experimental-strip-types` (sem Jest, sem Vitest) | [`handbook/reference/nodejs/`](../../../handbook/reference/nodejs/) | +| Runner usado no projeto: `node:test` nativo, `.ts` direto sem flag (sem Jest, sem Vitest) | [`handbook/reference/nodejs/`](../../../handbook/reference/nodejs/) | | Comandos do dia-a-dia (`pnpm test`, single-test com `--test-name-pattern`) | [`../../rules/testing.md`](../../rules/testing.md) | | Estrutura: `tests/` espelha `src/`, sufixo `.test.ts` é descoberto, `.contract.ts`/`.suite.ts` são suites parametrizadas reutilizáveis | [`../../../CLAUDE.md`](../../../CLAUDE.md) §"Convenções de testes" | | Exemplos vivos de testes na base | `tests/modules/contracts/domain/shared/money.test.ts`, `period.test.ts`, `bucket-name.test.ts`, `storage-key.test.ts` | diff --git a/.claude/skills/test-pyramid-engineer/SKILL.md b/.claude/skills/test-pyramid-engineer/SKILL.md index cfc100379..cb3e9cdaf 100644 --- a/.claude/skills/test-pyramid-engineer/SKILL.md +++ b/.claude/skills/test-pyramid-engineer/SKILL.md @@ -98,7 +98,7 @@ Ver [`modules/anti-padroes-locais.md`](modules/anti-padroes-locais.md). | Contract tests parametrizados (1 suíte, 2 adapters) | `tests/modules/contracts/adapters/persistence/contract-repository.suite.ts` + `inmemory.test.ts` + `drizzle-mysql.test.ts` | | E2E rodando a CLI real | `tests/cli/contracts.cli.test.ts` (memory) · `tests/cli/contracts.cli.mysql.test.ts` (integration) | | Regressão dirigida por defeito | `tests/regression/reports-2026-05-15.test.ts` | -| Runner: `node:test` nativo + `--experimental-strip-types` (sem Jest/Vitest) | [`handbook/reference/nodejs/`](../../../handbook/reference/nodejs/) | +| Runner: `node:test` nativo, `.ts` direto sem flag (sem Jest/Vitest) | [`handbook/reference/nodejs/`](../../../handbook/reference/nodejs/) | --- diff --git a/.claude/skills/ts-domain-modeler/SKILL.md b/.claude/skills/ts-domain-modeler/SKILL.md index e6514050e..c5555e430 100644 --- a/.claude/skills/ts-domain-modeler/SKILL.md +++ b/.claude/skills/ts-domain-modeler/SKILL.md @@ -44,7 +44,7 @@ Tabela de orientação rápida: | Tópico | Onde olhar | | :--- | :--- | -| Regras transversais do código (zero `throw`, zero `class`, `Result`, branded, ESLint flat config + typescript-eslint strict + type-checked) | [`../../../CLAUDE.md`](../../../CLAUDE.md) | +| Regras transversais do código (zero `throw`, zero `class`, `Result`, branded, oxlint type-aware + gate de AST `tests/cleanup/lint-gaps.test.ts`) | [`../../../CLAUDE.md`](../../../CLAUDE.md) | | Stack (Node 24 LTS, TS 6.0, ESM/NodeNext, pnpm) | [`handbook/reference/nodejs/`](../../../handbook/reference/nodejs/), [`handbook/reference/pnpm/`](../../../handbook/reference/pnpm/) | | Roadmap TS 7 (tsgo / Go-based compiler) | [`ADR-0009`](../../../handbook/architecture/adr/0009-node-24-typescript-6-with-7-roadmap.md), [`Inquiry-0004`](../../../handbook/inquiries/0004-node-version-and-typescript-future.md) | | Modular monolith + ports/adapters (fronteira de quem você é) | [`ADR-0006`](../../../handbook/architecture/adr/0006-modular-monolith-core-api.md) | @@ -178,7 +178,7 @@ import type { Result } from '../../../../shared/result.ts'; } ``` -Esse padrão é nativo Node 18+, funciona com `--experimental-strip-types`, e dispensa qualquer transpiler/mapper externo. +Esse padrão é nativo Node 18+, funciona sob o type stripping do Node, e dispensa qualquer transpiler/mapper externo. ### Exemplo concreto do módulo `contracts` diff --git a/.claude/skills/ts-domain-modeler/references/ts-esm-nodenext.md b/.claude/skills/ts-domain-modeler/references/ts-esm-nodenext.md index d79cfbe40..c544b9c08 100644 --- a/.claude/skills/ts-domain-modeler/references/ts-esm-nodenext.md +++ b/.claude/skills/ts-domain-modeler/references/ts-esm-nodenext.md @@ -55,7 +55,7 @@ Por que cada flag importa: | Flag | Motivo | | :--- | :--- | | `module: NodeNext` | Resolução nativa de ESM do Node | -| `noEmit: true` | TS só verifica; runtime usa `--experimental-strip-types` | +| `noEmit: true` | TS só verifica; o runtime usa o type stripping nativo do Node | | `allowImportingTsExtensions: true` | Permite `import x from './foo.ts'` | | `verbatimModuleSyntax: true` | Força `import type` para tipos, evita "fantasma" | | `exactOptionalPropertyTypes: true` | `foo?: string` ≠ `foo?: string \| undefined` | @@ -67,7 +67,7 @@ Por que cada flag importa: ## 3. Imports — sempre com `.ts` -Em ESM puro Node, **a extensão é obrigatória no import**. Como rodamos via `node --experimental-strip-types`, usamos `.ts` direto. +Em ESM puro Node, **a extensão é obrigatória no import**. Como rodamos os arquivos via `node arquivo.ts` (type stripping nativo), usamos `.ts` direto. ```ts // ✅ Correto @@ -146,13 +146,15 @@ Pontos: pnpm exec tsc --noEmit # Rodar arquivo TS direto -node --experimental-strip-types src/main.ts +node src/main.ts # Rodar testes do node:test em arquivos .test.ts -node --test --experimental-strip-types --no-warnings 'src/**/*.test.ts' +node --test 'src/**/*.test.ts' ``` -`--experimental-strip-types` (Node 22.6+) remove anotações de tipo em tempo de carga. É o que permite usar `.ts` direto, sem build. Sem dependências de transpiler (esbuild, swc, tsx). +O type stripping do Node remove anotações de tipo em tempo de carga, e é **default desde o Node +23.6** (backport na 22.18) — nenhuma flag é necessária. É o que permite usar `.ts` direto, sem +build e sem dependência de transpiler (esbuild, swc, tsx). > **Trade-off:** strip-types só **remove tipos** — não transforma `enum`, `namespace`, decorators legacy. Como nosso domínio é TS funcional puro, não usamos nada disso. Para Application+Adapters (que vão usar NestJS depois), provavelmente buildaremos para `dist/`. @@ -229,6 +231,6 @@ Antes de fechar uma sessão: | CommonJS | Sistema de módulos legacy do Node (`require`) | | NodeNext | Modo de resolução do TS que segue regras do Node moderno | | `verbatimModuleSyntax` | Força clareza sobre imports tipo vs. valor | -| `--experimental-strip-types` | Flag do Node 22.6+ que remove tipos em runtime | +| type stripping | Remoção de tipos em tempo de carga, default no Node 23.6+ (sem flag) | | Barrel export | `index.ts` que reexporta API pública de uma pasta | | `isolatedModules` | Garante que cada arquivo possa ser compilado isoladamente | diff --git a/.claude/skills/ts-quality-checker/SKILL.md b/.claude/skills/ts-quality-checker/SKILL.md index a09e3a9d3..4c7285dfe 100644 --- a/.claude/skills/ts-quality-checker/SKILL.md +++ b/.claude/skills/ts-quality-checker/SKILL.md @@ -22,7 +22,7 @@ Você roda o gate e reporta o resultado **sem interpretar**. Veredito é binári ```bash pnpm run typecheck # tsc --noEmit pnpm run format:check # prettier --check . -pnpm run lint # eslint . --cache +pnpm run lint # oxlint (type-aware, sem cache) pnpm test # node --test 'tests/**/*.test.ts' ``` @@ -37,8 +37,7 @@ gera. Para investigar um teste específico: ```bash -node --test --experimental-strip-types --enable-source-maps --no-warnings \ - --test-name-pattern="" tests/caminho/do/arquivo.test.ts +node --test --test-name-pattern="" tests/caminho/do/arquivo.test.ts ``` --- diff --git a/.dockerignore b/.dockerignore index d4276adc4..9c64030e2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -12,7 +12,7 @@ node_modules/ .pnpm-store/ .npm/ -# Build artifacts ─ Fase 1 roda via --experimental-strip-types, sem dist/ +# Build artifacts ─ o runtime executa .ts direto (type stripping do Node), sem dist/ dist/ build/ *.tsbuildinfo @@ -44,7 +44,7 @@ CLAUDE.md .editorconfig .prettierignore .prettierrc.json -eslint.config.js +.oxlintrc.json .eslintcache # Estado local do CLI (NUNCA enviar — pode conter dados sensíveis) diff --git a/.githooks/commit-msg b/.githooks/commit-msg index e75075a32..4eb920ad7 100755 --- a/.githooks/commit-msg +++ b/.githooks/commit-msg @@ -34,4 +34,4 @@ if [ -z "${NODE_BIN}" ]; then fi [ -x "${NODE_BIN}" ] || exit 0 -exec "${NODE_BIN}" --experimental-strip-types --no-warnings "${GATE}" "${MSG_FILE}" +exec "${NODE_BIN}" "${GATE}" "${MSG_FILE}" diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index b8e1e005c..359036e86 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -42,6 +42,13 @@ jobs: - name: Checkout # ADR-0011 §"Pin de versões" — pin por SHA. uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - name: Node (executa .ts direto, sem flag) — versão lida do .nvmrc + # Sem este passo o job rodava no Node pré-instalado da imagem do runner, que muda sem + # aviso — exatamente o "o CI escolhe sozinho" que o ADR-0073 §2 encerra. Era o único + # workflow que invoca pnpm e não declarava o runtime. + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: .nvmrc - name: Habilita pnpm (corepack — packageManager pinado em package.json) run: corepack enable - name: Audit (dependencies de runtime ≥ high) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91c81ba43..3a955601b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,27 +28,17 @@ jobs: - name: Checkout # ADR-0011 §"Pin de versões" — pin por SHA. uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - name: Node 24 (runtime do --experimental-strip-types) + - name: Node (executa .ts direto, sem flag) — versão lida do .nvmrc uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: '24' + # `node-version-file` e nunca um literal: um major solto (`'24'`) flutua a cada release + # upstream e um valor exato duplica o .nvmrc. Cobrado por + # tests/cleanup/node-version-single-source.test.ts. + node-version-file: .nvmrc - name: Habilita pnpm (corepack lê a versão do packageManager) run: corepack enable - name: Install (frozen-lockfile) run: pnpm install --frozen-lockfile - # Sem esta restauração o `--cache` do lint não vale NADA aqui: o runner nasce limpo, - # o cache é sempre frio, e o gate paga os ~44s cheios (Inquiry-0029, §4). - # A chave inclui o lockfile e a config porque ambos invalidam o cache do ESLint; - # `restore-keys` aceita cache de outro commit — o ESLint revalida por arquivo. - - name: Restaura cache do ESLint - # ADR-0011 §"Pin de versões" — pin por SHA. - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: .eslintcache - key: eslint-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.js', 'tsconfig.json') }}-${{ github.sha }} - restore-keys: | - eslint-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.js', 'tsconfig.json') }}- - eslint-${{ runner.os }}- - name: Typecheck run: pnpm run typecheck - name: Format check diff --git a/.github/workflows/commit-policy.yml b/.github/workflows/commit-policy.yml index 4a6a9d6db..c4f9df265 100644 --- a/.github/workflows/commit-policy.yml +++ b/.github/workflows/commit-policy.yml @@ -4,7 +4,7 @@ name: commit-policy # - PR com label `ai-assisted` → todo commit do range precisa de Assisted-by bem-formado. # - PR sem a label → só valida o FORMATO quando presente. # Roda tambem em labeled/unlabeled para reavaliar quando a label muda. Actions SHA-pinadas (ADR-0011). -# Zero dependencia: script Node puro (--experimental-strip-types) lendo os trailers via `git log`. +# Zero dependencia: script Node puro (`node arquivo.ts`, sem flag) lendo os trailers via `git log`. on: pull_request: @@ -32,11 +32,13 @@ jobs: - name: Setup Node uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: 24 + # Versão vem do .nvmrc, nunca de literal — ver + # tests/cleanup/node-version-single-source.test.ts. + node-version-file: .nvmrc - name: Verifica trailers Assisted-by dos commits do PR env: BASE_SHA: ${{ github.event.pull_request.base.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} PR_AI_ASSISTED: ${{ contains(github.event.pull_request.labels.*.name, 'ai-assisted') }} - run: node --experimental-strip-types --no-warnings scripts/ci/check-commit-trailers.ts + run: node scripts/ci/check-commit-trailers.ts diff --git a/.github/workflows/integration-notifications.yml b/.github/workflows/integration-notifications.yml index 9e1fbfaba..18d990621 100644 --- a/.github/workflows/integration-notifications.yml +++ b/.github/workflows/integration-notifications.yml @@ -29,10 +29,12 @@ jobs: steps: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - name: Node 24 (runtime do --experimental-strip-types) + - name: Node (executa .ts direto, sem flag) — versão lida do .nvmrc uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: '24' + # Versão vem do .nvmrc, nunca de literal — ver + # tests/cleanup/node-version-single-source.test.ts. + node-version-file: .nvmrc - name: Habilita pnpm (corepack lê a versão do packageManager) run: corepack enable - name: Install (frozen-lockfile) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 0fb066d3c..2d8f144f3 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -62,14 +62,16 @@ jobs: # SHA já em uso em ci.yml:30 (v6.0.3). Pin por digest (ADR-0011). uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - name: Node 24 (runtime do --experimental-strip-types) + - name: Node (executa .ts direto, sem flag) — versão lida do .nvmrc # SHA já em uso em ci.yml:32 (v6.4.0). SEM `cache: pnpm`: essa opção roda # `pnpm store path` DENTRO do setup-node, antes do `corepack enable` abaixo — e aí # o pnpm ainda não existe no PATH ("Unable to locate executable file: pnpm"). O ci.yml # não usa cache pelo mesmo motivo; seguimos o padrão provado do repo. uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: '24' + # Versão vem do .nvmrc, nunca de literal — ver + # tests/cleanup/node-version-single-source.test.ts. + node-version-file: .nvmrc - name: Habilita pnpm (corepack lê a versão do packageManager) run: corepack enable @@ -100,7 +102,7 @@ jobs: # → finally: down -v. O `down -v` é INOFENSIVO aqui (VM efêmera), destrutivo # só em dev (#500). Exit code != 0 = vermelho, MESMO com "fail 0/cancelled 1" # no sumário do node:test (o caso do #521). Confia-se SÓ no exit code. - run: node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts "${{ matrix.suite }}" + run: node --enable-source-maps scripts/ci/test-integration.ts "${{ matrix.suite }}" - name: Diagnóstico do compose em caso de falha # Roda só se o step anterior falhou (`if: failure()`). Sem mascarar exit code: diff --git a/.gitignore b/.gitignore index f25a2130e..c277ffd4c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,8 @@ dist/ .idea/ coverage/ .tsbuildinfo -# Cache do ESLint (~1 MB). Derivado do fonte e da config — nunca versionado. -# No CI ele é restaurado por actions/cache, não pelo repositório (Inquiry-0029). +# Resíduo do ESLint (removido no ADR-0072): clones anteriores à troca ainda têm o arquivo no +# disco. Ignorado para não aparecer como não rastreado; o oxlint não usa cache. .eslintcache # Dados brutos temporários (ex.: dump IBGE baixado por scripts/data/fetch-*) diff --git a/.nvmrc b/.nvmrc index b832e4001..df6ae3370 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24.16.0 +24.21.0 diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 000000000..8361a3831 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,430 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript"], + "categories": { + "correctness": "off", + "suspicious": "off", + "pedantic": "off", + "style": "off", + "restriction": "off", + "perf": "off", + "nursery": "off" + }, + "options": { + "typeAware": true + }, + "ignorePatterns": [ + "node_modules/**", + "dist/**", + "coverage/**", + "*.config.js", + "cli-state.json", + "tests/reports/**", + "tools/bugs-scripts/**", + ".tmp/**", + "handbook/specs/**", + ".claude/**", + "epic/**", + ".semgrep/**" + ], + "rules": { + "eslint/for-direction": "error", + "eslint/no-async-promise-executor": "error", + "eslint/no-case-declarations": "error", + "eslint/no-compare-neg-zero": "error", + "eslint/no-cond-assign": ["error", "except-parens"], + "eslint/no-constant-binary-expression": "error", + "eslint/no-constant-condition": [ + "error", + { + "checkLoops": "allExceptWhileTrue" + } + ], + "eslint/no-control-regex": "error", + "eslint/no-debugger": "error", + "eslint/no-delete-var": "error", + "eslint/no-dupe-else-if": "error", + "eslint/no-duplicate-case": "error", + "eslint/no-empty": [ + "error", + { + "allowEmptyCatch": false + } + ], + "eslint/no-empty-character-class": "error", + "eslint/no-empty-pattern": [ + "error", + { + "allowObjectPatternsAsParameters": false + } + ], + "eslint/no-empty-static-block": "error", + "eslint/no-ex-assign": "error", + "eslint/no-extra-boolean-cast": ["error", {}], + "eslint/no-fallthrough": [ + "error", + { + "allowEmptyCase": false, + "reportUnusedFallthroughComment": false + } + ], + "eslint/no-global-assign": [ + "error", + { + "exceptions": [] + } + ], + "eslint/no-invalid-regexp": ["error", {}], + "eslint/no-irregular-whitespace": [ + "error", + { + "skipComments": false, + "skipJSXText": false, + "skipRegExps": false, + "skipStrings": true, + "skipTemplates": false + } + ], + "eslint/no-loss-of-precision": "error", + "eslint/no-misleading-character-class": [ + "error", + { + "allowEscape": false + } + ], + "eslint/no-nonoctal-decimal-escape": "error", + "eslint/no-prototype-builtins": "error", + "eslint/no-regex-spaces": "error", + "eslint/no-self-assign": [ + "error", + { + "props": true + } + ], + "eslint/no-shadow-restricted-names": [ + "error", + { + "reportGlobalThis": true + } + ], + "eslint/no-sparse-arrays": "error", + "eslint/no-unassigned-vars": "error", + "eslint/no-unsafe-finally": "error", + "eslint/no-unsafe-optional-chaining": [ + "error", + { + "disallowArithmeticOperators": false + } + ], + "eslint/no-unused-labels": "error", + "eslint/no-unused-private-class-members": "error", + "eslint/no-useless-assignment": "error", + "eslint/no-useless-backreference": "error", + "eslint/no-useless-catch": "error", + "eslint/no-useless-escape": [ + "error", + { + "allowRegexCharacters": [] + } + ], + "eslint/preserve-caught-error": [ + "error", + { + "requireCatchParameter": false + } + ], + "eslint/require-yield": "error", + "eslint/use-isnan": [ + "error", + { + "enforceForIndexOf": false, + "enforceForSwitchCase": true + } + ], + "eslint/valid-typeof": [ + "error", + { + "requireStringLiterals": false + } + ], + "eslint/no-var": "error", + "eslint/prefer-const": [ + "error", + { + "destructuring": "any", + "ignoreReadBeforeAssign": false + } + ], + "eslint/prefer-rest-params": "error", + "eslint/prefer-spread": "error", + "typescript/await-thenable": "error", + "typescript/ban-ts-comment": [ + "error", + { + "minimumDescriptionLength": 10 + } + ], + "eslint/no-array-constructor": "error", + "typescript/no-array-delete": "error", + "typescript/no-base-to-string": "error", + "typescript/no-confusing-void-expression": "error", + "typescript/no-deprecated": "error", + "typescript/no-duplicate-enum-values": "error", + "typescript/no-duplicate-type-constituents": "error", + "typescript/no-dynamic-delete": "error", + "typescript/no-empty-object-type": "error", + "typescript/no-explicit-any": "error", + "typescript/no-extra-non-null-assertion": "error", + "typescript/no-extraneous-class": "error", + "typescript/no-floating-promises": "error", + "typescript/no-for-in-array": "error", + "typescript/no-implied-eval": "error", + "typescript/no-invalid-void-type": "error", + "typescript/no-meaningless-void-operator": "error", + "typescript/no-misused-new": "error", + "typescript/no-misused-promises": "error", + "typescript/no-misused-spread": "error", + "typescript/no-mixed-enums": "error", + "typescript/no-namespace": "error", + "typescript/no-non-null-asserted-nullish-coalescing": "error", + "typescript/no-non-null-asserted-optional-chain": "error", + "typescript/no-non-null-assertion": "error", + "typescript/no-redundant-type-constituents": "error", + "typescript/no-require-imports": "error", + "typescript/no-this-alias": "error", + "typescript/no-unnecessary-boolean-literal-compare": "error", + "typescript/no-unnecessary-condition": "error", + "typescript/no-unnecessary-template-expression": "error", + "typescript/no-unnecessary-type-arguments": "error", + "typescript/no-unnecessary-type-constraint": "error", + "typescript/no-unnecessary-type-conversion": "error", + "typescript/no-unnecessary-type-parameters": "error", + "typescript/no-unsafe-argument": "error", + "typescript/no-unsafe-assignment": "error", + "typescript/no-unsafe-call": "error", + "typescript/no-unsafe-declaration-merging": "error", + "typescript/no-unsafe-enum-comparison": "error", + "typescript/no-unsafe-function-type": "error", + "typescript/no-unsafe-member-access": "error", + "typescript/no-unsafe-return": "error", + "typescript/no-unsafe-unary-minus": "error", + "eslint/no-unused-expressions": [ + "error", + { + "allowShortCircuit": false, + "allowTaggedTemplates": false, + "allowTernary": false + } + ], + "eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ], + "eslint/no-useless-constructor": "error", + "typescript/no-useless-default-assignment": "error", + "typescript/no-wrapper-object-types": "error", + "typescript/only-throw-error": "error", + "typescript/prefer-as-const": "error", + "typescript/prefer-literal-enum-member": "error", + "typescript/prefer-namespace-keyword": "error", + "typescript/prefer-promise-reject-errors": "error", + "typescript/prefer-reduce-type-parameter": "error", + "typescript/prefer-return-this-type": "error", + "typescript/related-getter-setter-pairs": "error", + "typescript/require-await": "error", + "typescript/restrict-plus-operands": [ + "error", + { + "allowAny": false, + "allowBoolean": false, + "allowNullish": false, + "allowNumberAndString": false, + "allowRegExp": false + } + ], + "typescript/restrict-template-expressions": [ + "error", + { + "allowNumber": true, + "allowBoolean": true, + "allowNullish": false, + "allowAny": false + } + ], + "typescript/return-await": ["error", "error-handling-correctness-only"], + "typescript/triple-slash-reference": "error", + "typescript/unbound-method": "error", + "typescript/unified-signatures": "error", + "typescript/use-unknown-in-catch-callback-variable": "error", + "typescript/adjacent-overload-signatures": "error", + "typescript/array-type": "error", + "typescript/ban-tslint-comment": "error", + "typescript/class-literal-property-style": "error", + "typescript/consistent-generic-constructors": "error", + "typescript/consistent-indexed-object-style": "error", + "typescript/consistent-type-assertions": "error", + "typescript/consistent-type-definitions": "error", + "typescript/no-confusing-non-null-assertion": "error", + "eslint/no-empty-function": [ + "error", + { + "allow": [] + } + ], + "typescript/no-inferrable-types": "error", + "typescript/non-nullable-type-assertion-style": "error", + "typescript/prefer-find": "error", + "typescript/prefer-for-of": "error", + "typescript/prefer-function-type": "error", + "typescript/prefer-includes": "error", + "typescript/prefer-nullish-coalescing": "error", + "typescript/prefer-optional-chain": "error", + "typescript/prefer-regexp-exec": "error", + "typescript/prefer-string-starts-ends-with": "error", + "eslint/no-restricted-imports": [ + "error", + { + "paths": [ + { + "name": "axios", + "message": "Proibida (ADR-0011 §4): supply chain compromise em mar/2026. Use `fetch` global." + }, + { + "name": "request", + "message": "Proibida (ADR-0011 §4): deprecated desde 2020. Use `fetch` global." + }, + { + "name": "moment", + "message": "Proibida (ADR-0011 §4): manutenção mínima. Use `Intl` ou `date-fns`." + }, + { + "name": "crypto-js", + "message": "Proibida (ADR-0011 §4): inativo. Use Web Crypto nativo (`crypto.subtle`)." + } + ], + "patterns": [ + { + "group": ["lodash", "lodash/*"], + "message": "Proibida (ADR-0011 §4): surface area enorme. O runtime nativo cobre." + } + ] + } + ], + "typescript/consistent-type-imports": [ + "error", + { + "prefer": "type-imports", + "fixStyle": "inline-type-imports" + } + ], + "eslint/no-param-reassign": [ + "error", + { + "props": false + } + ], + "typescript/switch-exhaustiveness-check": "error", + "typescript/strict-boolean-expressions": [ + "error", + { + "allowString": false, + "allowNumber": false, + "allowNullableObject": false, + "allowNullableBoolean": false, + "allowNullableString": false, + "allowNullableNumber": false, + "allowAny": false + } + ], + "typescript/consistent-type-exports": [ + "error", + { + "fixMixedExportsWithInlineTypeSpecifier": true + } + ], + "typescript/no-import-type-side-effects": "error", + "typescript/method-signature-style": ["error", "property"], + "typescript/promise-function-async": "error", + "typescript/strict-void-return": "error", + "typescript/explicit-function-return-type": [ + "error", + { + "allowExpressions": true, + "allowTypedFunctionExpressions": true, + "allowHigherOrderFunctions": true + } + ], + "eslint/no-shadow": "error", + "eslint/no-use-before-define": [ + "error", + { + "functions": false, + "classes": true, + "variables": true, + "enums": true, + "typedefs": false, + "ignoreTypeReferences": true + } + ], + "eslint/no-loop-func": "error", + "eslint/default-param-last": "error", + "eslint/init-declarations": ["error", "always"], + "eslint/max-params": [ + "error", + { + "max": 4 + } + ], + "typescript/require-array-sort-compare": [ + "error", + { + "ignoreStringArrays": true + } + ], + "typescript/no-useless-empty-export": "error", + "typescript/prefer-readonly-parameter-types": [ + "error", + { + "checkParameterProperties": false, + "ignoreInferredTypes": true, + "treatMethodsAsReadonly": true + } + ] + }, + "overrides": [ + { + "files": ["src/modules/*/adapters/**/*.ts", "src/shared/adapters/**/*.ts"], + "rules": { + "typescript/require-await": "off" + } + }, + { + "files": ["src/shared/http/**/*.ts", "src/modules/*/adapters/http/**/*.ts"], + "rules": { + "typescript/require-await": "off", + "typescript/promise-function-async": "off", + "typescript/prefer-readonly-parameter-types": "off" + } + }, + { + "files": ["tests/**/*.ts"], + "rules": { + "typescript/no-floating-promises": "off", + "typescript/no-non-null-assertion": "off", + "typescript/no-unnecessary-condition": "off", + "eslint/no-unused-expressions": "off", + "typescript/unbound-method": "off", + "typescript/strict-boolean-expressions": "off", + "typescript/promise-function-async": "off", + "typescript/explicit-function-return-type": "off", + "eslint/init-declarations": "off", + "eslint/max-params": "off", + "typescript/prefer-readonly-parameter-types": "off" + } + } + ] +} diff --git a/.prettierignore b/.prettierignore index f1e481a57..2c73011e4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -8,7 +8,7 @@ cli-state.json # Worktrees git aninhados — cópias completas de src/tests de outras branches # (sessões paralelas). Têm seu próprio HEAD/formatação; nunca formatar pelo pai. -# Mesma categoria de .claude/worktrees e .agents/worktrees no eslint.config.js. +# Mesma categoria do `ignorePatterns` do .oxlintrc.json (`.claude/**`, `epic/**`). .claude/worktrees/ .agents/worktrees/ epic/ diff --git a/CLAUDE.md b/CLAUDE.md index 57a0142b5..66af5d479 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,7 +25,7 @@ Fonte única. Não replicar esta tabela em outro arquivo. | Eventos de domínio | **EN passado** — `ContractCreated` | | Commit | **PT-BR** com escopo — `feat(contracts): adiciona VO Money` | -Casing é enforced por `@typescript-eslint/naming-convention` — desligado em `tests/**`. +Casing é enforced por `tests/cleanup/lint-gaps.test.ts` (o `naming-convention`, que o oxlint não tem) — fora de `tests/**`. ## Política de regressão zero — invariante @@ -45,7 +45,7 @@ Todo commit gerado por IA leva o trailer `Assisted-by: AGENT_NAME:MODEL_VERSION` ## Anti-padrões — os que exigem julgamento -Os mecânicos (`npm`, sintaxe TS, casing, `class` no domínio, JSON/ENUM no MySQL) já são barrados por hook, `tsconfig`, ESLint e semgrep. +Os mecânicos (`npm`, sintaxe TS, casing, `class` no domínio, JSON/ENUM no MySQL) já são barrados por hook, `tsconfig`, oxlint, gate de AST (`tests/cleanup/lint-gaps.test.ts`) e semgrep. 1. **Escrever `npm` em doc, PR, script ou comentário** — sempre `pnpm`. O hook barra a execução, não o texto. 2. **Misturar módulos numa sessão** (`ctr_*` e `fin_*` ao mesmo tempo) — quebra o isolamento por prefixo. diff --git a/Dockerfile b/Dockerfile index d2082f12d..43b3afe6b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,16 @@ -# syntax=docker/dockerfile:1.10 +# syntax=docker/dockerfile:1 +# +# ⚠️ `:1` e NUNCA um minor fixo. A doc oficial é explícita: `docker/dockerfile:1` acompanha a +# última 1.x.x e o BuildKit checa atualização a cada build, enquanto "if a specific version is +# used, such as `1.2` or `1.2.1`, the Dockerfile needs to be updated manually to continue +# receiving bugfixes and new features" (https://docs.docker.com/build/buildkit/frontend/). +# Este arquivo ficou em `1.10` até 06/10/2026 — 17 minors atrás da ponta (`1.27.1`), sem receber +# correção desde que a `1.11` saiu, e ninguém tinha como notar. +# +# Por que isto NÃO contraria o digest pin do ADR-0011, que vale para a imagem base abaixo: o +# frontend do `# syntax` é baixado em tempo de BUILD e não entra na imagem final — ele não afeta a +# reprodutibilidade do que roda em produção, que é o que aquele ADR protege. Um minor fixo aqui +# não era nem imutável (recebe patches `1.10.x`) nem atualizado: era o pior dos dois mundos. # # core-api — módulo Contracts # ───────────────────────────────────────────────────────────────────────────── @@ -7,7 +19,7 @@ # stage `deps` não precisa de toolchain C++. # # Camadas: -# 1. base — pin do node:24.15-bookworm-slim por digest (ADR-0011 supply chain) +# 1. base — pin do node:24.21-bookworm-slim por digest (ADR-0011 supply chain) # 2. deps — instala dependências (sem toolchain C++) # 3. runtime — imagem final mínima, non-root, signal-safe # @@ -32,10 +44,15 @@ # ──────────────────────────────────────────────────────────────────────────── # Stage 1 — base # Pin: digest do índice multi-arch (amd64 + arm64), Debian 12 Bookworm Slim. -# Para atualizar: `docker buildx imagetools inspect node:24.15-bookworm-slim --format '{{.Manifest.Digest}}'` -# Digest atual (2026-06-07): sha256:4e6b70dd6cbfc88c8157ba19aa3d9f9cce6ba4703576d55459e45efcbc9c5f5d +# Para atualizar: `docker buildx imagetools inspect node:24.21-bookworm-slim --format '{{.Manifest.Digest}}'` +# Digest atual (2026-10-06): sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20 +# +# A major.minor desta tag é cobrada contra o `.nvmrc` por +# `tests/cleanup/node-version-single-source.test.ts` — subir aqui sem subir lá (ou vice-versa) fica +# vermelho no gate. O digest em si o teste NÃO cobra: a tag é reconstruída upstream a cada patch +# de Debian, e digest errado já falha o build com `manifest unknown`. # ──────────────────────────────────────────────────────────────────────────── -FROM node:24.15-bookworm-slim@sha256:4e6b70dd6cbfc88c8157ba19aa3d9f9cce6ba4703576d55459e45efcbc9c5f5d AS base +FROM node:24.21-bookworm-slim@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20 AS base # tini é o init mínimo (PID 1) para reaping de zumbis e forward de SIGTERM/SIGINT. # Disponível via apt em Debian — equivalente ao `apk add tini` do Alpine anterior. @@ -86,16 +103,29 @@ LABEL org.opencontainers.image.title="core-api" \ org.opencontainers.image.vendor="Envolve / Bem Comum" \ org.opencontainers.image.source="https://github.com/envolve/bem-comum-core-api" \ org.opencontainers.image.licenses="proprietary" \ - org.opencontainers.image.base.name="docker.io/library/node:24.15-bookworm-slim" + org.opencontainers.image.base.name="docker.io/library/node:24.21-bookworm-slim" # Variáveis de runtime. # - NODE_ENV=production: stripping de warnings, otimizações. -# - NODE_NO_WARNINGS=1: silencia avisos experimentais (strip-types em Node 24). -# - NODE_OPTIONS: habilita strip-types nativamente + suprime warning explícito -# (defesa em profundidade contra NODE_NO_WARNINGS ser desativado por debugger). +# +# Saíram daqui `--experimental-strip-types` e `NODE_NO_WARNINGS=1`/`--no-warnings`, medidos no +# runtime do `devEngines` (ver `tests/cleanup/node-flags-not-redundant.test.ts`): +# - `--experimental-strip-types` é redundante: o stripping é DEFAULT desde o Node 23.6 — a prova +# é `--no-strip-types` existir como negação no `--help`. O ENTRYPOINT abaixo executa `.ts` +# direto porque o runtime já faz isso. +# - `NODE_NO_WARNINGS=1` + `--no-warnings` existiam para calar o `ExperimentalWarning` do +# stripping, que não é mais emitido. Mantê-los passou a significar **engolir +# `DeprecationWarning` em produção** — justamente o canal pelo qual o Node anuncia o que +# quebra na próxima major, e a informação que torna acionável a disciplina de subida de +# runtime do ADR-0073. Em contêiner, esse aviso vai para o stderr → log do ECS. +# +# `--enable-source-maps` FICA, e a razão é medida: `drizzle-orm` publica 444 arquivos `.js.map` +# (mais 4 em `fast-xml-parser`). Sem a flag, um erro dentro da lib reporta o `.js` empacotado +# (`drizzle-orm/utils.js:109`); com ela, o `.ts` de origem (`src/utils.ts:208`). O código DESTE +# repositório não precisa dela — o type stripping preserva a posição —, mas ela nunca serviu a +# ele: serve às dependências que publicam mapa, e é em produção que o stack trace importa. ENV NODE_ENV=production \ - NODE_NO_WARNINGS=1 \ - NODE_OPTIONS="--experimental-strip-types --no-warnings" + NODE_OPTIONS="--enable-source-maps" # Copia node_modules do estágio deps. COPY --from=deps /app/node_modules ./node_modules @@ -132,8 +162,8 @@ STOPSIGNAL SIGTERM HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ CMD node -e "fetch('http://127.0.0.1:3000/health').then(r=>{process.exit(r.ok?0:1)}).catch(()=>process.exit(1))" -# `tini` como PID 1, depois `node` com flags (NODE_OPTIONS habilita strip-types). ENTRYPOINT -# é o binário Node direto (sem shell) — encaminha sinais corretamente sem shell-trap. +# `tini` como PID 1, depois `node` executando o `.ts` direto (stripping é default no Node 24). +# ENTRYPOINT é o binário Node direto (sem shell) — encaminha sinais corretamente sem shell-trap. # # CLI-RETIRE-EMBEDDED (ADR-0037): a CLI embutida foi removida; o entrypoint é o servidor HTTP. # O worker de outbox roda em processo dedicado: `node src/modules/contracts/worker/run.ts` diff --git a/Procfile b/Procfile index 76e8746d9..76338dae9 100644 --- a/Procfile +++ b/Procfile @@ -1,6 +1,6 @@ -http: node --experimental-strip-types --enable-source-maps --no-warnings src/server.ts -outbox-contracts: node --experimental-strip-types --enable-source-maps --no-warnings src/modules/contracts/worker/run.ts -outbox-partners: node --experimental-strip-types --enable-source-maps --no-warnings src/modules/partners/worker/run.ts -supplier-projection: node --experimental-strip-types --enable-source-maps --no-warnings src/workers/supplier-view-projection/run.ts -contract-count-projection: node --experimental-strip-types --enable-source-maps --no-warnings src/workers/contract-count-projection/run.ts -email-dispatch: node --experimental-strip-types --enable-source-maps --no-warnings src/workers/email-dispatch/run.ts +http: node --enable-source-maps src/server.ts +outbox-contracts: node --enable-source-maps src/modules/contracts/worker/run.ts +outbox-partners: node --enable-source-maps src/modules/partners/worker/run.ts +supplier-projection: node --enable-source-maps src/workers/supplier-view-projection/run.ts +contract-count-projection: node --enable-source-maps src/workers/contract-count-projection/run.ts +email-dispatch: node --enable-source-maps src/workers/email-dispatch/run.ts diff --git a/README.md b/README.md index 9339b4b0e..968c30c09 100644 --- a/README.md +++ b/README.md @@ -132,10 +132,10 @@ pnpm install # respeita pnpm-lock.yaml pnpm install --frozen-lockfile # em CI # Gate de qualidade — os quatro, nesta ordem -pnpm run typecheck # tsc --noEmit (strict completo) +pnpm run typecheck # tsc --noEmit (TypeScript 7, strict completo) pnpm run format:check # prettier --check . -pnpm run lint # eslint . (flat config, typescript-eslint strict + type-checked) -pnpm test # tests/**/*.test.ts via node:test + --experimental-strip-types +pnpm run lint # oxlint (.oxlintrc.json, type-aware via oxlint-tsgolint) +pnpm test # tests/**/*.test.ts via node:test (executa .ts direto) # Servidor + processos de background pnpm run serve # sobe a borda HTTP (node src/server.ts); config via env diff --git a/compose.yaml b/compose.yaml index dcf3fd254..fa2cbd7e0 100644 --- a/compose.yaml +++ b/compose.yaml @@ -438,8 +438,11 @@ services: # hardening (no-new-privileges, cap_drop ALL, read_only, tmpfs /tmp), network # core-api, depends_on mysql+http healthy (schema migrado). WORKER_GROUP via # environment; DATABASE_URLs injetadas por wrapper sh + $(cat /run/secrets/...) - # + exec tini. NODE_OPTIONS="--experimental-strip-types --no-warnings" herdado - # do ENV da imagem. read_only: strip-types não grava em disco; mysql2 é JS puro. + # + exec tini. Nenhum serviço daqui declara NODE_OPTIONS: o stripping de tipos é default no + # Node 24, então o `node` da imagem executa `.ts` sem flag. O que vale em runtime é o + # NODE_OPTIONS="--enable-source-maps" do Dockerfile (ENV, linha 128), herdado por todos — a + # variável não está vazia, só não carrega flag de strip-types. read_only: strip-types não grava + # em disco; mysql2 é JS puro. # ────────────────────────────────────────────────────────────────────────── # Grupo A — outbox (contracts + partners). 1 pool compartilhado. @@ -569,8 +572,8 @@ services: # da imagem é `tini -- node src/server.ts` — server.ts ESTÁ no entrypoint. Por isso # sobrescrevemos `entrypoint: [tini, --, node]` (PID 1 + runtime) e deixamos o # `command` apontar o script do job; sem o override, o `command` seria ANEXADO ao - # server.ts (subiria o servidor, não o sweeper). NODE_OPTIONS=--experimental-strip-types - # vem do ENV da imagem (herdado). Efetivo: `tini -- node src/jobs/.../run.ts`. + # server.ts (subiria o servidor, não o sweeper). Nenhuma flag é necessária no `node`: o + # stripping de tipos é default no Node 24. Efetivo: `tini -- node src/jobs/.../run.ts`. # # O sweeper usa CONTRACTS_DATABASE_URL_FILE (suportado pelo sweeper — único service # que suporta este mecanismo nativo). Os demais jobs usam o padrão sh+$(cat). diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index 1d62881ac..000000000 --- a/eslint.config.js +++ /dev/null @@ -1,352 +0,0 @@ -// Flat config — typescript-eslint v8 + ESLint 10 -// Docs: https://typescript-eslint.io/getting-started - -import eslint from '@eslint/js'; -import prettierConfig from 'eslint-config-prettier/flat'; -import tseslint from 'typescript-eslint'; - -export default tseslint.config( - { - ignores: [ - 'node_modules/**', - 'dist/**', - 'coverage/**', - '*.config.js', - 'cli-state.json', - // tests/reports/ contém scripts ad-hoc de investigação (probes, dumps), - // não código de produção/teste. - 'tests/reports/**', - // tools/bugs-scripts/ — scripts de diagnóstico black-box descartáveis - // (já gitignored); não entram em nenhum tsconfig (projectService falha). - 'tools/bugs-scripts/**', - // .tmp/ — scratchpad DENTRO do repositório (gitignored). É onde script de - // medição/conversão tem de viver: no scratchpad da sessão o ESM resolve pelo path - // do ARQUIVO e dá MODULE_NOT_FOUND nos imports do projeto. Mesmo caso de - // tools/bugs-scripts/ acima — descartável, fora de qualquer tsconfig, o - // projectService type-aware falha. Sem esta linha, todo script ad-hoc em .tmp/ - // reprova o `lint` e trava o gate do hook Stop. - '.tmp/**', - // handbook/specs/ guarda o histórico de especificação das features. Os - // contracts/*.ts de lá são esboços de port para documentar design — nunca - // entraram em tsconfig algum, e o projectService type-aware falha neles. - // (Viviam na raiz como `specs/` até 2026-08-06, quando o spec-kit saiu e o - // acervo virou documento no handbook.) - 'handbook/specs/**', - // Worktrees de sessões paralelas do Claude Code são cópias completas de - // src/ e tests/ de outras branches. O .gitignore já as exclui, mas o flat - // config do ESLint NÃO lê .gitignore — sem isto o projectService type-aware - // ingere ~1748 .ts extras e estoura o heap do V8 (OOM no `pnpm run lint`). - '.claude/**', - // Worktrees de épico criados na raiz (`epic/`, convenção do dev): - // mesmo caso dos worktrees do Claude — cópia completa de src/ e tests/ de - // outra branch. Sem esta exclusão o projectService ingere os .ts extras e - // estoura o heap (OOM no lint), além de lintar código de outra branch. - 'epic/**', - // Fixtures do gate Semgrep (.semgrep/tests/**) — TS/SH com violações de ADR - // INTENCIONAIS (mysqlEnum, json column) para `semgrep --test`. Não entram em - // nenhum tsconfig; o projectService type-aware falharia neles. - '.semgrep/**', - ], - }, - - // Base — JS recommended - eslint.configs.recommended, - - // TypeScript — strict + type-aware - ...tseslint.configs.strictTypeChecked, - ...tseslint.configs.stylisticTypeChecked, - - { - languageOptions: { - parserOptions: { - projectService: true, - tsconfigRootDir: import.meta.dirname, - }, - }, - rules: { - // =========================================================== - // Regras invariantes do projeto (CLAUDE.md raiz) - // =========================================================== - 'no-restricted-syntax': [ - 'error', - { - selector: 'ClassDeclaration', - message: - 'Classes são proibidas no projeto — use `type Readonly<{}>` + funções standalone.', - }, - { - selector: 'ClassExpression', - message: - 'Classes são proibidas no projeto — use `type Readonly<{}>` + funções standalone.', - }, - ], - - // Libs proibidas — ADR-0011 §4 (supply-chain hardening). - 'no-restricted-imports': 'off', - '@typescript-eslint/no-restricted-imports': [ - 'error', - { - paths: [ - { - name: 'axios', - message: - 'Proibida (ADR-0011 §4): supply chain compromise em mar/2026. Use `fetch` global.', - }, - { - name: 'request', - message: 'Proibida (ADR-0011 §4): deprecated desde 2020. Use `fetch` global.', - }, - { - name: 'moment', - message: 'Proibida (ADR-0011 §4): manutenção mínima. Use `Intl` ou `date-fns`.', - }, - { - name: 'crypto-js', - message: 'Proibida (ADR-0011 §4): inativo. Use Web Crypto nativo (`crypto.subtle`).', - }, - ], - patterns: [ - { - group: ['lodash', 'lodash/*'], - message: 'Proibida (ADR-0011 §4): surface area enorme. O runtime nativo cobre.', - }, - ], - }, - ], - - 'prefer-const': 'error', - 'no-var': 'error', - '@typescript-eslint/no-explicit-any': 'error', - - '@typescript-eslint/consistent-type-imports': [ - 'error', - { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, - ], - - '@typescript-eslint/no-unnecessary-type-assertion': 'off', - '@typescript-eslint/dot-notation': 'off', - - '@typescript-eslint/restrict-template-expressions': [ - 'error', - { - allowNumber: true, - allowBoolean: true, - allowNullish: false, - allowAny: false, - }, - ], - - 'no-param-reassign': ['error', { props: false }], - '@typescript-eslint/explicit-module-boundary-types': 'off', - - // =========================================================== - // 🟢 Recomendadas — alinham com regras invariantes - // =========================================================== - '@typescript-eslint/switch-exhaustiveness-check': 'error', - '@typescript-eslint/strict-boolean-expressions': [ - 'error', - { - allowString: false, - allowNumber: false, - allowNullableObject: false, - allowNullableBoolean: false, - allowNullableString: false, - allowNullableNumber: false, - allowAny: false, - }, - ], - '@typescript-eslint/consistent-type-exports': [ - 'error', - { fixMixedExportsWithInlineTypeSpecifier: true }, - ], - '@typescript-eslint/no-import-type-side-effects': 'error', - '@typescript-eslint/method-signature-style': ['error', 'property'], - '@typescript-eslint/promise-function-async': 'error', - '@typescript-eslint/strict-void-return': 'error', - '@typescript-eslint/explicit-function-return-type': [ - 'error', - { - allowExpressions: true, - allowTypedFunctionExpressions: true, - allowHigherOrderFunctions: true, - }, - ], - - // =========================================================== - // 🟡 Opcionais ativadas (decisão do user) - // =========================================================== - // Estas regras têm versões ESLint base + typescript-eslint. Desabilitar base. - 'no-shadow': 'off', - '@typescript-eslint/no-shadow': 'error', - - 'no-use-before-define': 'off', - '@typescript-eslint/no-use-before-define': [ - 'error', - { - functions: false, // permite hoisting comum de declarações - classes: true, - variables: true, - enums: true, - typedefs: false, // tipos são hoisted; permitir uso antes da declaração - ignoreTypeReferences: true, - }, - ], - - 'no-loop-func': 'off', - '@typescript-eslint/no-loop-func': 'error', - - 'default-param-last': 'off', - '@typescript-eslint/default-param-last': 'error', - - 'init-declarations': 'off', - '@typescript-eslint/init-declarations': ['error', 'always'], - - // no-redeclare: `type X` + `const X` é declaration merging válido em TS - // (pattern central do projeto: Money/Period/ContractId etc). Desabilitado. - 'no-redeclare': 'off', - '@typescript-eslint/no-redeclare': 'off', - - 'max-params': 'off', - '@typescript-eslint/max-params': ['error', { max: 4 }], - - '@typescript-eslint/require-array-sort-compare': ['error', { ignoreStringArrays: true }], - - '@typescript-eslint/no-useless-empty-export': 'error', - - '@typescript-eslint/member-ordering': 'error', - - // Brand types e Date não casam bem com Readonly completo. - // Mantemos ativado com flags conservadoras; se gerar muito ruído, - // baixar para 'warn'. - '@typescript-eslint/prefer-readonly-parameter-types': [ - 'error', - { - checkParameterProperties: false, - ignoreInferredTypes: true, - treatMethodsAsReadonly: true, - }, - ], - - '@typescript-eslint/naming-convention': [ - 'error', - { - selector: 'default', - format: ['camelCase'], - leadingUnderscore: 'allow', - trailingUnderscore: 'forbid', - }, - { - selector: 'variable', - format: ['camelCase', 'UPPER_CASE', 'PascalCase'], - leadingUnderscore: 'allow', - }, - { - selector: 'function', - format: ['camelCase', 'PascalCase'], - }, - { - selector: 'parameter', - format: ['camelCase'], - leadingUnderscore: 'allow', - }, - { - selector: 'typeLike', - format: ['PascalCase'], - }, - { - selector: 'typeProperty', - format: ['camelCase'], - leadingUnderscore: 'allow', - filter: { regex: '[- ]', match: false }, - }, - { - selector: 'objectLiteralProperty', - format: null, // dicionário pode ter qualquer formato (kebab-case PT) - }, - { - selector: 'enumMember', - format: ['PascalCase'], - }, - { - selector: 'import', - format: ['camelCase', 'PascalCase'], - }, - ], - - // =========================================================== - // Unused vars (preferimos a versão typescript-eslint) - // =========================================================== - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - caughtErrorsIgnorePattern: '^_', - }, - ], - }, - }, - - // ----------------------------------------------------------- - // Adapters — async sem await é design legítimo (InMemory) - // ----------------------------------------------------------- - { - files: ['src/modules/*/adapters/**/*.ts', 'src/shared/adapters/**/*.ts'], - rules: { - '@typescript-eslint/require-await': 'off', - }, - }, - - // ----------------------------------------------------------- - // Borda HTTP — adapter Fastify (ADR-0025): tipos externos mutáveis - // (FastifyRequest/FastifyReply) e handlers que retornam reply.send() - // (promise) sem await. Mesmas folgas dos demais adapters. - // ADR-0028: shell transversal em src/shared/http; HTTP de feature em - // src/modules//adapters/http. - // ----------------------------------------------------------- - { - files: ['src/shared/http/**/*.ts', 'src/modules/*/adapters/http/**/*.ts'], - rules: { - '@typescript-eslint/prefer-readonly-parameter-types': 'off', - '@typescript-eslint/promise-function-async': 'off', - '@typescript-eslint/require-await': 'off', - }, - }, - - // ----------------------------------------------------------- - // Testes — node:test design + narrowing depois de cast - // ----------------------------------------------------------- - { - files: ['tests/**/*.ts'], - rules: { - '@typescript-eslint/no-floating-promises': 'off', - // Handlers/fixtures inline podem retornar promise sem async. - '@typescript-eslint/promise-function-async': 'off', - '@typescript-eslint/no-unnecessary-condition': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-unused-expressions': 'off', - // Testes podem ter parâmetros não-readonly em fixtures - '@typescript-eslint/prefer-readonly-parameter-types': 'off', - // Testes podem ter funções inline sem return type explícito - '@typescript-eslint/explicit-function-return-type': 'off', - // Testes podem ter > 4 params em fixtures construtoras - '@typescript-eslint/max-params': 'off', - // Naming em testes pode usar 'c', 'r', 'a' como aliases - '@typescript-eslint/naming-convention': 'off', - // strict-boolean-expressions em assert.equal pode reclamar de Date|null - '@typescript-eslint/strict-boolean-expressions': 'off', - // init-declarations: testes podem ter let sem init em alguns helpers - '@typescript-eslint/init-declarations': 'off', - // Spy+restore de metodo de objeto (ex.: `const restore = process.stderr.write; ...; - // process.stderr.write = restore;`) e idioma de teste: o metodo nunca e chamado - // destacado, so reatribuido ao mesmo objeto -> o aviso de `this`-binding e falso-positivo. - '@typescript-eslint/unbound-method': 'off', - }, - }, - - // ----------------------------------------------------------- - // Prettier — DESLIGA regras de estilo que conflitam com o formatter. - // Deve ser o ÚLTIMO config (sobrescreve tudo). - // ----------------------------------------------------------- - prettierConfig, -); diff --git a/handbook/architecture/adr/0072-oxc-lint-supersedes-inquiry-0029-and-0067-d2.md b/handbook/architecture/adr/0072-oxc-lint-supersedes-inquiry-0029-and-0067-d2.md new file mode 100644 index 000000000..a7b9ee959 --- /dev/null +++ b/handbook/architecture/adr/0072-oxc-lint-supersedes-inquiry-0029-and-0067-d2.md @@ -0,0 +1,104 @@ +[← Voltar para ADRs](./README.md) + +# ADR-0072: O lint passa ao `oxlint` type-aware e o TypeScript 7 entra sem side-by-side — supersede a decisão de linter da Inquiry-0029 e o D2 do ADR-0067 + +- **Status:** Accepted +- **Date:** 2026-10-05 (aceito no mesmo dia pelo Tech Lead) +- **Deciders:** Gabriel Aderaldo (Tech Lead) — as quatro decisões de escopo abaixo (paridade 1:1, gates de AST, API `unstable/` do TS 7, fatiamento em PRs) · agente assistente — medição e redação +- **Supersedes (parcial):** [ADR-0067](./0067-typescript-7-side-by-side-supersedes-0009-language.md) — **só o D2** (o TS 6 retido por alias para o `typescript-eslint`). D1 (TS 7 é o compilador do gate), D3 (`@typescript/native-preview` sai), D4 (um checker só), D5 (risco do template literal com emoji) e D6 (quarentena sem exceção) **seguem vigentes**, e este ADR é o que finalmente os implementa. +- **Supersedes:** a decisão da [Inquiry-0029](../../inquiries/0029-linter-type-aware-sob-typescript-7.md) §6 (permanecer no ESLint + `--cache`). +- **Conformidade com:** [ADR-0011](./0011-supply-chain-hardening.md) §5 (justificativa de dependência nova) · [ADR-0029](./0029-pnpm-11-supply-chain-defaults.md) (quarentena e `trustPolicy`) + +--- + +## Contexto + +### O ADR-0067 está aceito há 41 dias e o código nunca o implementou + +Medido em 2026-10-05 na `origin/dev` (`fbbca89d`): o `package.json` declara `typescript ^6.0.0`, `@typescript/native-preview 7.0.0-dev.20260515.1`, `eslint` e `typescript-eslint`. Nem o D1 (TS 7 como compilador) nem o D3 (`native-preview` fora) chegaram ao código. A razão é estrutural: o único caminho compatível com o ESLint é o alias do D2, e o próprio 0067 declara o preço dele — rebaixamento efetivo do compilador do lint para `6.0.2`, duas cópias do compilador, e o nome `typescript` deixando de significar o que aparenta. + +### A Inquiry-0029 decidiu pela velocidade do `--cache` — e o `--cache` não entrega o que ela mediu + +A 0029 (`decided` em 2026-08-06) descartou o `oxlint` com um argumento só, escrito por ela mesma: _"a razão para migrar era velocidade, e a velocidade estava disponível sem migrar"_ (§4.3c), com `eslint . --cache` quente em 1,5–2,0 s. Nenhum dos três gatilhos de reavaliação que ela fixou disparou (o lint quente segue em ~1 s; o TS 7.1 com API estável não saiu; não apareceu regra que o `typescript-eslint` não cubra). O que mudou foi a **base** da decisão, medida hoje: + +| Medição (2026-10-05) | Resultado | +| :--- | :--- | +| `eslint . --cache` quente, local | 1,0–1,3 s — confirma a 0029 | +| `eslint .` sem cache, local | 30–32 s | +| Passo `Lint` no CI, últimos 4 runs da `dev` | **39–62 s** — o log diz `Cache not found for input keys` até no prefixo genérico `eslint-Linux-`: o cache do CI **nunca é restaurado** | +| Sonda controlada: `b.ts` chama função de `a.ts`; muda **só** `a.ts` para retornar `Promise` | `eslint --cache` → **exit 0**; `eslint` sem cache → **exit 1** (`no-floating-promises` em `b.ts`) | + +A última linha é a que decide. O `--cache` do ESLint guarda resultado **por arquivo**, e a regra type-aware depende de tipo declarado **noutro** arquivo. A documentação do `typescript-eslint` diz isso literalmente — _"Typed lint rules almost always have dependencies on types across files in practice. ESLint's caching doesn't account for those cross-file dependencies."_ ([FAQ](https://typescript-eslint.io/troubleshooting/faqs/eslint)). A configuração que a 0029 escolheu **dá falso verde** no gate local e **não acelera nada** no CI. + +### A tentativa de 04/09/2026 não é precedente a seguir + +As branches `chore/oxlint-ts7-migration` e `docs/tsdoc-contracts-financial` migraram a mesma toolchain e nunca viraram PR: 31 commits num dia, 483 arquivos, misturando toolchain, versões de pnpm/Node, TSDoc em 1300 funções, manifesto de deploy e bump de dependência; contradiziam a 0029 sem supersedê-la; ligavam categorias inteiras do oxlint (7.468 achados) e perderam o `no-restricted-imports` do ADR-0011 sem que ninguém notasse, porque ele já dava zero. Este ADR é feito do avesso daquilo: um assunto, porte regra a regra, prova por diferencial. + +## Decisão + +**D1 — O lint é o `oxlint` com type-aware (`oxlint-tsgolint`), em versão exata** — `oxlint 1.86.0`, `oxlint-tsgolint 7.0.2003`. `pnpm run lint` é `oxlint`; sem cache, porque não precisa: o repositório inteiro (2036 arquivos) roda em **2,3 s** com type-aware ligado. + +**D2 — O `.oxlintrc.json` é paridade 1:1 com o ESLint de hoje, GERADO, não escrito.** O inventário saiu do próprio `eslint --print-config` nos cinco contextos do flat config (base, scripts, adapters, borda HTTP, testes): **158 regras ativas**. **154** têm equivalente no oxlint e foram portadas com a mesma severidade, as mesmas opções e as mesmas exceções por pasta (`overrides`). Todas as `categories` do oxlint ficam **desligadas** e só os plugins necessários ligados — o lint cobra o que o ESLint cobrava, nem mais, nem menos. Regra nova entra por decisão, uma por vez. + +**D3 — As quatro regras sem equivalente têm destino explícito:** + +| Regra do ESLint | Destino | +| :--- | :--- | +| `no-octal` | o compilador já recusa — `TS1121: Octal literals are not allowed` no TS 6 e no TS 7 (medido) | +| `no-restricted-syntax` (proíbe `class`) | gate de AST `tests/cleanup/lint-gaps.test.ts` | +| `@typescript-eslint/naming-convention` | idem, com a configuração do projeto portada da implementação do plugin (`validator.js`, `format.js`) | +| `@typescript-eslint/member-ordering` | idem, ordem padrão para `interface` e type literal | + +Os três gates leem a AST do **TypeScript 7 pela API `typescript/unstable/*`**, concentrada num único arquivo (`tests/support/ts-ast.ts`). Exceção de nome é **allowlist com motivo** dentro do gate (hoje duas: `__brand` e `__nonZeroMoney`), cobrada nos dois sentidos — exceção que deixou de corresponder a um achado reprova. + +**D4 — O `typescript` é o 7, em versão exata (`7.0.2`), sem alias.** Revoga o D2 do ADR-0067: o alias existia só porque o `typescript-eslint` exige TS 6 como biblioteca (peer `<6.1.0`), e o ESLint sai. Nenhum código do repositório importa `typescript` como biblioteca além dos gates de D3 — é por eles que a versão é exata: uma faixa deixaria a resolução trocar a superfície `unstable/` sem decisão. + +**D5 — Saem do manifesto** `eslint`, `typescript-eslint`, `eslint-config-prettier`, `@eslint/js` e `@typescript/native-preview`; sai o `eslint.config.js`. O **Prettier fica** — a troca de formatador é assunto de outro PR, com ADR próprio. + +**D6 — O que o oxlint acusa e o ESLint deixava passar é corrigido, não silenciado.** Foram 16 achados (17, contando um já coberto por diretiva justificada): 7 `prefer-optional-chain`, 4 `no-deprecated`, 3 `no-unnecessary-type-parameters`, 2 `prefer-readonly-parameter-types`, 1 `consistent-generic-constructors`. ⚠️ Os 4 `no-deprecated` são o `.meta({ example })` do Zod, depreciado pelo `zod-openapi` em favor de `examples` — **a correção muda o OpenAPI gerado** (`example` → `examples`, o formato do OpenAPI 3.1). + +## Verificação + +A paridade foi medida, não presumida: + +| Prova | Resultado | +| :--- | :--- | +| **Diferencial** — as ~221 diretivas `eslint-disable` desligadas nos dois linters, comparando arquivo + linha + regra | 222 de 224 violações reais achadas pelos dois **no mesmo lugar**; as 2 restantes são `naming-convention`, cobertas por D3 | +| **Sondas de opção** — par positivo/negativo por regra com opção customizada (ex.: 4 parâmetros têm de passar com `max: 4`; o default do oxlint é 3) | **36 de 36**, conferidas também contra o ESLint | +| **Gate de nomes** contra o ESLint, com a regra forçada também em `tests/` | **46 de 46** nomes idênticos em 2036 arquivos, zero divergência nos dois sentidos | +| **Fixtures dos três gates** conferidos contra o ESLint | 15 de 15 combinações fixture × regra | +| **Prova invertida** — violação plantada das três regras num arquivo versionado | os três testes reprovam, com arquivo e linha | +| Gate completo na branch | `typecheck` · `format:check` · `lint` · `test` (11.904 pass, 0 fail, 20 skip) | + +## Consequências + +### Positivas + +- **O falso verde do `--cache` deixa de existir** — o lint sempre olha o repositório inteiro, com tipo. +- **Lint em 2,3 s** contra 30–32 s do ESLint sem cache e 39–62 s no CI. +- **O ADR-0067 sai do papel:** um compilador só, o TS 7, com `typecheck` em ~1,9 s (o TS 6 levava ~7,2 s na mesma máquina). +- **Menos dependências:** por máquina instalada saem 81 pacotes e entram 6 (`oxlint`, `oxlint-tsgolint`, `typescript` e o binário nativo de cada um). No lockfile o saldo é −33 entradas, porque ele lista os binários de todas as plataformas. +- Duas afirmações de `enforced_by` que já eram falsas (ADR-0037 e ADR-0047 apontavam o `eslint.config.js`, que nunca cobriu fronteira de camada) passam a `[]`, que é o estado real. + +### Negativas — declaradas, não omitidas + +- **Os três gates dependem de API `unstable/`.** Um upgrade do `typescript` pode quebrá-los. A quebra é ruidosa (o gate tem guarda contra vacuidade e o autoteste exige linha exata), e o pin exato faz do upgrade um ato deliberado. +- **A fidelidade dos três gates passa a ser nossa.** O ESLint era a referência; ela foi congelada nos fixtures do autoteste enquanto ainda existia. Mudança de config de nomes exige mudar o gate **e** os fixtures. +- **O type-aware do oxlint se declara de cobertura incompleta** ([docs](https://oxc.rs/docs/guide/usage/linter/type-aware.html)) e três das regras portadas são `nursery` (`no-unnecessary-condition`, `prefer-optional-chain`, `no-useless-assignment`). Um upgrade do oxlint pode mudar o que elas acusam — de novo, por isso a versão exata. +- O checker embutido no `oxlint-tsgolint` não é o `tsc` do gate; uma divergência entre os dois não foi procurada além do que o diferencial cobre. + +## Alternativas rejeitadas + +- **Manter o ESLint e implementar o D2 do ADR-0067 (alias do TS 6).** Mantém o `--cache` com falso verde, ou o lint de 30–60 s sem ele, e herda o rebaixamento e a dívida de legibilidade que o próprio 0067 declarou. +- **ESLint sem `--cache`.** Correto, mas é o lint mais lento do gate, e continua exigindo o alias do TS 6. +- **Ligar categorias do oxlint** (`correctness`, `suspicious`, `pedantic`) em vez de portar regra a regra — é o caminho de setembro: 7.468 achados para triar, regras do projeto perdidas no meio, e uma cobertura que ninguém consegue comparar com a anterior. +- **`naming-convention` por plugin JS do oxlint.** Os plugins JS são alpha, não aceitam regra type-aware, e o pacote do `typescript-eslint` traria de volta o peer `typescript <6.1.0`. +- **Aceitar a perda do casing.** O CLAUDE.md declara o casing como enforced; perder o mecanismo o devolveria a norma em prosa. +- **Biome** — mesma avaliação da Inquiry-0029 §5 (inferência própria e parcial, falso-negativo silencioso). + +## Quando Re-avaliar + +- **TS 7.1 com API programática estável:** `tests/support/ts-ast.ts` troca de `unstable/` para a API estável. +- **[tsgolint#186](https://github.com/oxc-project/tsgolint/issues/186) fechar** (`naming-convention` no oxlint): o gate de nomes pode dar lugar à regra nativa — depois de o diferencial contra os fixtures passar. +- **Upgrade de major do oxlint, ou promoção/remoção das regras `nursery` portadas.** +- O formatador: o PR que trocar o Prettier pelo `oxfmt` traz ADR próprio. diff --git a/handbook/architecture/adr/0073-nvmrc-single-source-supersedes-0058-partial.md b/handbook/architecture/adr/0073-nvmrc-single-source-supersedes-0058-partial.md new file mode 100644 index 000000000..96b2ce35b --- /dev/null +++ b/handbook/architecture/adr/0073-nvmrc-single-source-supersedes-0058-partial.md @@ -0,0 +1,127 @@ +[← Voltar para ADRs](./README.md) + +# ADR-0073: O `.nvmrc` é a fonte única da versão do runtime — as demais declarações derivam + +- **Status:** Accepted (`supersedes` **parcialmente** [ADR-0058](./0058-runtime-tracks-recommended-lts.md): apenas a §2, que enumera os pontos de declaração, e a §4, que define o que o gate cobra. A §1 — acompanhar o LTS recomendado por subida gradual — e a §3 — troca de tecnologia justificada por inquiry — seguem **integralmente vigentes**) +- **Date:** 2026-10-06 +- **Deciders:** Gabriel (dono do repo) +- **Contexto de origem:** medição da divergência entre dev, CI e produção durante a migração de toolchain da branch `chore/toolchain-oxc-ts7` + +## Contexto + +O [ADR-0058](./0058-runtime-tracks-recommended-lts.md) resolveu o problema certo — tirou a versão do runtime dos ADRs e a pôs onde é executável — e enumerou três pontos de declaração (§2): + +| Onde | O que declara | +| --- | --- | +| `package.json` → `engines.node` | o piso aceito | +| `Dockerfile` → `FROM node:` | o que roda em produção | +| `.github/workflows/*.yml` → `node-version` | o que o CI usa | + +A §4 mandou cobrar a concordância entre eles por gate, e o gate foi escrito (`tests/cleanup/node-version-single-source.test.ts`). **Dois meses depois, dev e produção rodavam versões diferentes com o gate verde.** Medido em 06/10/2026: + +| Declaração | Valor | Observação | +| --- | --- | --- | +| `.nvmrc` | `24.16.0` | **não está na tabela da §2** | +| `package.json` → `devEngines.runtime.version` | `24.16.0` | **não está na tabela da §2** | +| `package.json` → `engines.node` | `>=24.0.0` | | +| `Dockerfile` → `FROM` | `node:24.15-bookworm-slim` | o que roda em hml e prod | +| `Dockerfile` → `LABEL image.base.name` | `node:24.15-bookworm-slim` | o que os scanners de CVE leem | +| 4 workflows → `node-version` | `'24'` / `24` | preso a nenhuma das duas; flutua a cada release upstream | + +Três causas, todas de forma, nenhuma de julgamento: + +1. **A §2 omitiu dois pontos que já existiam.** `.nvmrc` e `devEngines.runtime` estavam no repositório em 05/08/2026, quando o ADR-0058 foi aceito. Ficando fora da tabela, ficaram fora do gate — e são justamente os dois que fixam versão **exata**, enquanto os três listados são faixa, tag de minor e major solto. + +2. **O gate cobrava só o major, e a divergência era de minor.** A decisão era deliberada e está escrita no docstring da versão anterior: exigir patch idêntico produziria vermelho a cada release do Node. O argumento valia enquanto cada ponto carregava o número **escrito à mão** — três cópias com três cadências. O efeito foi que o ADR-0058:41 registrou a divergência como "observação, não norma", disse que a §4 existia para torná-la visível, e ela passou verde por dois meses. + +3. **`node-version` no CI é uma cópia do número, qualquer que seja a forma.** Um major solto (`'24'`) delega ao `setup-node` a escolha do runtime, que muda sem aviso e sem diff. Um valor exato (`24.21.0`) resolve a flutuação criando **quatro cópias novas** do número, uma por workflow — e cópia de número é a causa raiz desta própria divergência. + +O calendário tornou isto urgente em vez de estético: a linha 24 sai de **Active LTS em 20/10/2026** e a 26 entra em **28/10/2026** (`nodejs/Release`, `schedule.json`). A §1 do ADR-0058 manda acompanhar o LTS recomendado — e cumpri-la exigia, até aqui, editar o mesmo número em cinco lugares sem nada capaz de dizer que um ficou atrás. + +## Decisão + +### 1. A versão do runtime vive no `.nvmrc`, e só nele — invariante + +O `.nvmrc` **MUST** conter a versão **exata** (`major.minor.patch`), sem prefixo `v`. É a fonte única. + +A escolha não é arbitrária: é o único arquivo que as duas pontas da cadeia leem **sem tradução humana** — o `fnm` o resolve no `cd` e o `actions/setup-node` o resolve por `node-version-file`. Qualquer outro candidato exigiria que alguém copiasse o número. + +### 2. As demais declarações derivam, e nenhuma é fonte — invariante + +| Onde | Deriva como | Por que não pode ser exata | +| --- | --- | --- | +| `package.json` → `devEngines.runtime.version` | **exata**, igual ao `.nvmrc` | — | +| `package.json` → `engines.node` | piso por **major** (`>=.0.0`) | é contrato de compatibilidade, não pin | +| `Dockerfile` → `FROM node:.-…` | **major.minor** | a tag oficial do Node não publica patch | +| `Dockerfile` → `LABEL image.base.name` | igual à tag do `FROM` | — | +| `package.json` → `@types/node` | faixa do **major** | segue o versionamento do DefinitelyTyped | +| `.github/workflows/*.yml` | `node-version-file: .nvmrc` | **não escreve número algum** | + +Um workflow que use `actions/setup-node` **MUST NOT** declarar `node-version`, em nenhuma forma — nem major solto, nem valor exato. A §2 do ADR-0058, que listava `node-version` como ponto de declaração, é o que esta seção supersede. + +### 3. O gate cobra a derivação na granularidade de cada alvo — invariante + +`tests/cleanup/node-version-single-source.test.ts` **MUST** conferir cada declaração da §2 contra o `.nvmrc`, cada uma na granularidade que pode expressar. Isto supersede a §4 do ADR-0058, que cobrava apenas o major entre três pontos. + +A objeção que justificava cobrar só o major — vermelho a cada release — **deixa de se aplicar** quando existe fonte única: subir o runtime é editar um arquivo, e o gate confere as derivadas contra ele em vez de comparar cópias entre si. + +O gate **MUST** manter guarda contra verde por vacuidade. Não é zelo retórico: a guarda anterior (`há workflows declarando node-version`) foi **o único teste a falhar** quando esta migração trocou a forma do CI, e sem ela a troca teria deixado o gate verde por não achar o que verificar. + +### 4. O que continua fora do alcance mecânico — declarado + +Igual ao ADR-0058 §4, e pela mesma razão: **se o major é o LTS recomendado** exige consultar a rede, e o gate local é offline e determinístico por desenho. Um repositório inteiro coerente numa versão EOL passa em todos os gates. + +A **pendência de CI agendado** declarada no ADR-0058 segue aberta e não é fechada aqui. + +O **digest** do `FROM` também não é cobrado: a tag é reconstruída upstream a cada patch de Debian, e um gate tag↔digest nasceria vermelho no primeiro rebuild. Digest errado falha o build com `manifest unknown` — ruidoso, na hora. + +> **Observação, não norma — e datada de propósito:** na data desta decisão o `.nvmrc` passou de `24.16.0` a `24.21.0`, e as cinco derivadas foram alinhadas no mesmo diff. Este ADR **não** escreve a versão-alvo em ponto normativo algum: fazê-lo repetiria o defeito que o ADR-0058 §2 corrigiu, e que a alternativa B dele registrou como rejeitada. + +## Consequências + +### Positivas + +- **Subir o runtime passa a ser editar um arquivo.** As cinco derivadas são conferidas por gate; esquecer uma fica vermelho em vez de ficar invisível por dois meses. +- **O CI deixa de escolher sozinho.** `node-version-file` acaba com a classe de defeito em que o runner troca de runtime sem diff e sem aviso. +- **A divergência de minor passa a ser detectável.** Era o vão exato por onde dev `24.16.0` e produção `24.15` conviveram com o gate verde. +- **Os pontos que a §2 omitia entram na cobertura.** `.nvmrc` e `devEngines` eram os únicos com versão exata e eram os dois que ninguém conferia. +- **O `LABEL` e o `@types/node` entram junto.** Um label divergente manda Trivy/Scout/Snyk auditar uma imagem que não está no ar; `@types/node` de outra major faz o typecheck aprovar API que não existe em execução. + +### Negativas, declaradas + +1. **Mais um ponto de falha no CI.** Se o `.nvmrc` for apagado ou malformado, todo workflow com `setup-node` falha de uma vez. É falha ruidosa e imediata, e o gate cobra a forma do arquivo — mas o acoplamento é real e substitui N falhas independentes por uma central. +2. **`node-version-file` exige `actions/setup-node` v4+.** O repositório está em v6.4.0, pinado por SHA; a restrição é satisfeita hoje e passa a ser requisito para qualquer downgrade da action. +3. **O Dockerfile e o `devEngines` ainda escrevem o número.** A derivação é **conferida**, não **gerada** — ver alternativa C. Duas cópias permanecem, e o que muda é que agora elas falham quando divergem. +4. **A granularidade heterogênea é carga cognitiva.** Quem sobe a versão precisa saber que o Dockerfile leva `major.minor` e o `devEngines` leva a exata. O gate diz qual está errada, mas a assimetria existe porque os alvos a impõem. + +### Neutras + +- A escolha de **Node como runtime** (ADR-0002) e o critério de **acompanhar o LTS recomendado** (ADR-0058 §1) não são reabertos. Este ADR muda **onde** a versão vive e **como** a concordância é cobrada. +- A §2 é enunciada para o runtime Node. O **pnpm** continua sob o seu próprio arranjo de três pontos (`packageManager`, `engines.pnpm`, `ENV PNPM_VERSION`, cobrado por `tests/cleanup/supply-chain-settings.test.ts`), e **não** há aqui decisão de unificar os dois modelos — seria ADR próprio. + +## Alternativas Consideradas + +### A. Manter o literal do ADR-0058 (`node-version` no CI) + +Rejeitada. Não escrever ADR é tentador porque o ADR-0058 é recente e bem construído, mas manter `node-version: '24'` preserva metade do defeito: o CI continua flutuando a cada release upstream, e a divergência de minor — a que de fato ocorreu — continua fora do alcance do gate. + +### B. Pinar o valor exato nos workflows (`node-version: 24.21.0`) + +Rejeitada, e é a alternativa que parece mais segura. Resolve a flutuação e respeita o literal da §2, mas cria **quatro cópias novas** do número: cada subida passaria a tocar oito lugares em vez de cinco. Cópia de número é a causa raiz desta divergência, e a resposta não pode ser multiplicá-la. + +### C. Gerar as derivadas por script a partir do `.nvmrc` + +Rejeitada **por ora**, e é a alternativa mais forte. Um `scripts/ci/sync-node-version.ts` eliminaria as duas cópias restantes (Dockerfile, `devEngines`) em vez de apenas conferi-las. Fica fora porque o `FROM` carrega **digest**, que não é derivável do `.nvmrc` — precisa de `docker buildx imagetools inspect`, isto é, de rede e de Docker no caminho. Um gerador que resolve duas das três partes da linha e deixa a terceira para o humano é mais frágil que um conferidor que cobra as três. Candidata a reabrir quando houver resolução de digest offline no CI. + +### D. Deixar a política na rule de supply-chain, sem ADR + +Rejeitada, pela mesma razão que o ADR-0058 rejeitou a sua alternativa D: `.claude/rules/` é regra operacional por path e não carrega *ratio legis*. A razão desta decisão — "fonte única em vez de N cópias concordantes" — é o conteúdo mais valioso dela, e supersedir parcialmente um ADR aceito exige ADR, não rule. + +## Gatilho de reavaliação + +Este ADR **MUST** ser reaberto por um ADR que o supersede se **qualquer uma** destas ocorrer: + +1. Surgir resolução de digest offline que torne a alternativa C viável — a derivação passa de conferida a gerada, e a §3 muda de conteúdo. +2. O `actions/setup-node` descontinuar `node-version-file`, tornando a §2 inaplicável ao CI. +3. O projeto adotar runtime sem equivalente a `.nvmrc`, tornando a §1 sem alvo. +4. A granularidade heterogênea da §2 provar-se fonte de erro recorrente — isto é, se o gate acusar repetidamente a mesma confusão entre `major.minor` e versão exata, indicando que o arranjo pede geração em vez de conferência. diff --git a/handbook/architecture/adr/README.md b/handbook/architecture/adr/README.md index 491732863..336fa9cbe 100644 --- a/handbook/architecture/adr/README.md +++ b/handbook/architecture/adr/README.md @@ -150,7 +150,7 @@ Um **ADR (Architecture Decision Record)** é um documento curto que captura uma | [0055](./0055-cognito-external-idp-supersedes-0024-authn.md) | **Amazon Cognito como autoridade de autenticação** (decisão de diretoria) — supersede **parcial** do 0024 (fonte de identidade, emissão de token, refresh opaco). **A autorização permanece 100% no `core-api`**: nenhuma claim de RBAC no token, `cognito:groups` não é fonte de authZ. Vínculo por coluna neutra `external_subject` com find-or-link JIT que **nunca cria usuário**. Transição com dois verificadores isolados (ES256 legado × RS256 Cognito), flags fail-secure e desligamento em **3 superfícies**. Estado do PKCE em cookie selado `SameSite=Lax` (não em store); store compartilhado da sessão fica como dívida com gatilho na 2ª réplica | Accepted | 2026-07-29 | | [0056](./0056-ocr-port-adapter.md) | **OCR como Port/Adapter Pattern** — `OcrPort.extract(pdfUrl)` com adapters `mock → Tesseract → Textract`. **Criado como `ADR-0034` e renumerado em 2026-07-31** (colisão de número com a adoção do Bruno). Superseded pelo [0050](./0050-document-reader-cascade-supersedes-0034.md), que reorienta para cascata nativo-first e troca URL por bytes (anti-SSRF) | **Superseded by 0050** | 2026-06-06 | | [0057](./0057-claude-md-as-canonical-agent-doc.md) | **`CLAUDE.md` como doc canônica de agente** — o `AGENTS.md` é aposentado e deletado (o padrão aberto multi-ferramenta deixou de pagar seu custo quando o repo passou a suportar só o Claude Code). Sai do texto o que já é mecânico: tabelas de roteamento (descoberta nativa), sintaxe TS (`tsconfig`) e "não rode `npm`" (hook). Inclui **errata** de como ler as referências ao `AGENTS.md` nos ADRs [0037](./0037-http-first-retire-embedded-cli.md), [0040](./0040-agent-findings-as-github-issues.md) (onde `#15` passa a ser `#7`) e [0054](./0054-ai-assisted-contribution-policy.md), que são imutáveis e não foram editados | Accepted | 2026-08-03 | -| [0058](./0058-runtime-tracks-recommended-lts.md) | **O runtime acompanha o LTS recomendado — critério em vez de versão fixa.** Supersede **parcialmente** [0002](./0002-keep-nodejs-runtime.md) e [0009](./0009-node-24-typescript-6-with-7-roadmap.md): apenas a FORMA de fixar versão, não a escolha de Node nem de TypeScript. Nasce do inventário de decisões — `ADR-0002-C2` ("versão é Node 20 LTS") e `ADR-0009-C5` ("ajustes mínimos" na migração TS 7) foram para `contradicted` **pela mesma causa**: ADR que fixa versão ou estima esforço produz afirmação que envelhece sozinha. A versão passa a viver onde é executável (`engines.node`, `Dockerfile`, CI), e este ADR **deliberadamente não escreve a versão-alvo** — fazê-lo repetiria o defeito. Troca de tecnologia estrutural exige **inquiry que meça**, não que argumente. Concordância entre os três pontos cobrada por `tests/cleanup/node-version-single-source.test.ts` | Accepted | 2026-08-05 | +| [0058](./0058-runtime-tracks-recommended-lts.md) | **O runtime acompanha o LTS recomendado — critério em vez de versão fixa.** Supersede **parcialmente** [0002](./0002-keep-nodejs-runtime.md) e [0009](./0009-node-24-typescript-6-with-7-roadmap.md): apenas a FORMA de fixar versão, não a escolha de Node nem de TypeScript. Nasce do inventário de decisões — `ADR-0002-C2` ("versão é Node 20 LTS") e `ADR-0009-C5` ("ajustes mínimos" na migração TS 7) foram para `contradicted` **pela mesma causa**: ADR que fixa versão ou estima esforço produz afirmação que envelhece sozinha. A versão passa a viver onde é executável (`engines.node`, `Dockerfile`, CI), e este ADR **deliberadamente não escreve a versão-alvo** — fazê-lo repetiria o defeito. Troca de tecnologia estrutural exige **inquiry que meça**, não que argumente. Concordância entre os três pontos cobrada por `tests/cleanup/node-version-single-source.test.ts` | Accepted (§2 e §4 superseded por [0073](./0073-nvmrc-single-source-supersedes-0058-partial.md); §1 e §3 vigentes) | 2026-08-05 | | [0059](./0059-bff-aggregates-without-business-rules.md) | **O BFF agrega, mas não decide** — `supersedes` **parcial** do [0005](./0005-thin-bff-gateway.md): caem "Zero composição de respostas" e o alvo de 200-300 linhas; permanecem "zero regra de negócio", roteamento por prefixo e cross-cutting. "Burro" passa a significar **sem regra de negócio**, não sem composição — o BFF real ficou entre a opção 2 e a 3 que o 0005 enumerou, e o ADR não previa esse ponto. Composição para o client é papel canônico de BFF; o critério que separa é o do [0049](./0049-core-api-bff-boundary.md) ("o banco precisa fazer isso? → core; é montar o que já veio? → BFF"). O alvo numérico sai porque proxy de contagem envelhece sozinho — mesmo defeito de forma que o [0058](./0058-runtime-tracks-recommended-lts.md) documenta. **Nenhuma cláusula é verificável deste repositório** (o BFF vive fora), e isso está declarado | Accepted | 2026-08-05 | @@ -163,11 +163,13 @@ Um **ADR (Architecture Decision Record)** é um documento curto que captura uma | [0065](./0065-remittance-responsibility-boundary-supersedes-0060-0061-transmitted.md) | **A responsabilidade pela remessa termina no bucket** — `supersedes` **parcial** de [0060](./0060-van-transport-via-s3-bucket-supersedes-0008-relay.md) (`:78-79`) e [0061](./0061-van-bucket-contract-supersedes-0060-pendencies.md) (`:114`): a cláusula "a transição para `Transmitted` depende de sinal externo" deixa de valer para o **título** e segue valendo para a **remessa**. Decisão da P.O. em 24/08 ([#792](https://github.com/ERP-Bem-Comum/core-api/issues/792)), por ACL (Vernon, p. 142): "transmitido" significa **"saiu da nossa alçada"**, não "o banco confirmou". O título vira `Transmitted` **na geração, na mesma transação da reserva** (CAS `status = 'Approved'`, ADR-0063), com evento `PayableTransmitted` (a #823 vira projeção); `Failed` **não devolve**, `discard` devolve por CAS; pré-voo e read-model deixam de colapsar o estado. **`Pago` segue manual** ([#59](https://github.com/ERP-Bem-Comum/core-api/issues/59)), aceitando `Approved` e `Transmitted` como origem. **Download do arquivo em produção** ([#822](https://github.com/ERP-Bem-Comum/core-api/issues/822)): rota em todo ambiente sob permissão dedicada `remittance:download`, 403 `remittance-download-forbidden`, log estruturado de cada acesso, bytes pela API com `contentHash` — URL assinada rejeitada (0050 `:73`). ⚠️ "Transmitido" na tela não significa "o banco recebeu": a #787 sobe de prioridade | Accepted | 2026-08-24 | | [0066](./0066-semver-product-version-and-generated-changelog.md) | **A versão do produto é SemVer com tag anotada `vX.Y.Z`, e o `CHANGELOG.md` é gerado do git** — eixo **distinto** do [0033](./0033-api-versioning-v1-legacy-mirror.md), que versiona a API por recurso. Levantado em 25/08: o repositório nunca marcou versão — **zero** tags de versão (as 17 são `archive/*`/`backup/*`/`legacy/*`), **zero** releases, `package.json` em `0.1.0`, sem CHANGELOG, com `main` (que dispara o deploy) parada em `9acf6db1`, 23/07, a **572 commits** (**192** por `--first-parent`) da `dev`. Rejeita `1.0.0-RELEASE` por **incorreção**: pela regra 9 do SemVer tudo após o hífen é *pré-lançamento*, logo `1.0.0-RELEASE` **<** `1.0.0` — a forma é convenção Maven/Spring (`1.0.0.RELEASE`, com ponto). Rejeita **LTS**, que é política de suporte a linhas paralelas e não número de versão, num produto de um ambiente e um cliente. Escada `-alpha.N` → `-beta.N` → `-rc.N` → GA; `1.0.0` **é promessa**: depois dele quebrar contrato exige major, e foi sob a licença do `0.y.z` (regra 4) que o [#826](https://github.com/ERP-Bem-Comum/core-api/issues/826) entrou. CHANGELOG **derivado** por `pnpm release:notes`, que **erra para mostrar** — `chore` de escopo desconhecido ou ausente sai em seção visível, porque `chore(financial)` do [#855](https://github.com/ERP-Bem-Comum/core-api/pull/855) ligou download de dado bancário em produção — e **nomeia** o que não classificou. **A capacidade entra na `dev`; a declaração não** — gerador, suíte e política são infraestrutura, enquanto `package.json#version` e o `CHANGELOG.md` são afirmações sobre o artefato e só nascem no ato da promoção: mergear `1.0.0-rc.1` na `dev` faria todo commit posterior nascer dentro de uma rc que não o contém, e `0.1.0` na `dev` é **correto** enquanto nada foi publicado. ⚠️ **Não autoriza a promoção**: quem a libera é o CA0 da [#873](https://github.com/ERP-Bem-Comum/core-api/issues/873), o RBAC hoje fixado em `bypass` (`src/server.ts:161`). **A CERIMÔNIA de validação roda EM PRODUÇÃO** (decisão da equipe de negócio), então a ordem é o inverso do intuitivo: resolver o CA0 → promover a `-rc.N` para `main` (o deploy) → cerimônia **ponta a ponta até a VAN em produção** → declarar o GA sobre a **mesma árvore**. A promoção é pré-condição da cerimônia, não o contrário — e é por isso que `-rc.N` existe: a candidata **vai a produção sendo candidata**, e reprovando o recuo se chama `-rc.2` sem queimar o `1.0.0`. ⚠️ A ordem torna o CA0 **pré-requisito de deploy**, não de release, e faz a cerimônia esbarrar no CA2: gravar no `saida/` **é** enfileirar pagamento (ADR-0060) | Accepted | 2026-08-25 | -| [0067](./0067-typescript-7-side-by-side-supersedes-0009-language.md) | **TypeScript 7 nativo compila; o TS 6 fica só para o `typescript-eslint`** — `supersedes` **parcial** de [0009](./0009-node-24-typescript-6-with-7-roadmap.md), seções "Linguagem" (`:32-34`) e "Plano de migração" (`:36-42`). O gatilho literal do 0009 (`:90-96`) disparou: **TS 7.0 é GA desde 08/07/2026**. Mas duas previsões dele estavam erradas e ficam ditas: "ajustes mínimos" não descreve o port — **nenhuma** versão do `typescript-eslint` aceita TS 7 (peer `>=4.8.4 <6.1.0` no `latest` 8.68.0 **e** no canary; sem v9 publicada; issue [#10940](https://github.com/typescript-eslint/typescript-eslint/issues/10940) OPEN, parada desde 09/07) —, e o "CI roda `tsc` e `tsgo` em paralelo" (`:39`) **nunca foi implementado** em 4 meses, então é **revogado**, não repetido. Side-by-side por alias: `typescript` → `npm:@typescript/typescript6`, `@typescript/native` → `npm:typescript@^7.0.2`. **`@typescript/native-preview` sai** (descontinuado pela Microsoft). ✅ Nenhum breaking do TS 7 atinge o `tsconfig` (medido campo a campo: `ES2024`, `NodeNext`, sem `baseUrl`, `types`/`strict` explícitos) — dividendo do "strict total desde o dia 1". ⚠️ Risco declarado: template literal com emoji muda tipo inferido **sem erro** (TS 6 conta 4, TS 7 conta 3) — exposição **medida hoje: nula**, o repo não tem template literal type algum. ⚠️ Custo consciente: o alias só existe até `6.0.2` contra `6.0.3` instalado. Instalação **respeita a quarentena** de 24h (`minimumReleaseAgeStrict`) | Accepted | 2026-08-25 | +| [0067](./0067-typescript-7-side-by-side-supersedes-0009-language.md) | **TypeScript 7 nativo compila; o TS 6 fica só para o `typescript-eslint`** — `supersedes` **parcial** de [0009](./0009-node-24-typescript-6-with-7-roadmap.md), seções "Linguagem" (`:32-34`) e "Plano de migração" (`:36-42`). O gatilho literal do 0009 (`:90-96`) disparou: **TS 7.0 é GA desde 08/07/2026**. Mas duas previsões dele estavam erradas e ficam ditas: "ajustes mínimos" não descreve o port — **nenhuma** versão do `typescript-eslint` aceita TS 7 (peer `>=4.8.4 <6.1.0` no `latest` 8.68.0 **e** no canary; sem v9 publicada; issue [#10940](https://github.com/typescript-eslint/typescript-eslint/issues/10940) OPEN, parada desde 09/07) —, e o "CI roda `tsc` e `tsgo` em paralelo" (`:39`) **nunca foi implementado** em 4 meses, então é **revogado**, não repetido. Side-by-side por alias: `typescript` → `npm:@typescript/typescript6`, `@typescript/native` → `npm:typescript@^7.0.2`. **`@typescript/native-preview` sai** (descontinuado pela Microsoft). ✅ Nenhum breaking do TS 7 atinge o `tsconfig` (medido campo a campo: `ES2024`, `NodeNext`, sem `baseUrl`, `types`/`strict` explícitos) — dividendo do "strict total desde o dia 1". ⚠️ Risco declarado: template literal com emoji muda tipo inferido **sem erro** (TS 6 conta 4, TS 7 conta 3) — exposição **medida hoje: nula**, o repo não tem template literal type algum. ⚠️ Custo consciente: o alias só existe até `6.0.2` contra `6.0.3` instalado. Instalação **respeita a quarentena** de 24h (`minimumReleaseAgeStrict`) | Accepted (D2 superseded por 0072) | 2026-08-25 | | [0068](./0068-env-fail-fast-every-environment.md) | **Toda variável de ambiente lida é obrigatória, em TODO ambiente** — ausente ou recusada, o processo não sobe; e `X_DRIVER=memory` deixa de ser valor aceito. Fecha a [#799](https://github.com/ERP-Bem-Comum/core-api/issues/799) e **revoga o FR-007** do [#456](https://github.com/ERP-Bem-Comum/core-api/issues/456), que fazia `memory` declarado subir em produção "sem falhar". O fato que decide não está no código: **as envs de homologação e produção são postas à mão, na console da AWS, por um funcionário da Codebit** — degradar ali não protege ninguém, porque o sinal não chega a quem poderia corrigir; e local que degrada deixa de espelhar hml/prod, que é como um defeito de configuração sobrevive até o deploy. Rejeita a variante mais estrita (remover `driver: 'memory'` também dos seis tipos por módulo) por ser **cara e inútil**: custo medido de **179** arquivos de teste migrando para integração e `pnpm test` passando a exigir Docker, para trancar uma porta já trancada — em hml/prod a única entrada do in-memory é a env, e não existe caller que passe `{ driver: 'memory' }` fora do `server.ts` e dos testes ([Inquiry-0034](../../inquiries/0034-in-memory-fora-de-local-custo-na-piramide.md)). O adapter em memória **sobrevive como double de teste injetado** — outra fronteira, outra coisa. ⚠️ Custo declarado: todo dev passa a precisar declarar as envs, e um ambiente sem elas para de subir | Accepted | 2026-08-31 | | [0069](./0069-approval-policy-follows-rbac-bypass-supersedes-0052-partial.md) | **A alçada de aprovação segue o `AUTH_RBAC_MODE=bypass`** — `supersedes` **parcial** de [0052](./0052-rbac-bypass-flag.md): o bypass deixa de parar na borda HTTP e alcança a permissão `payable:approve` lida pela `approval-policy` do domínio. O bypass vinha sendo aplicado pela metade, por **três** camadas que decidem sobre permissão e das quais só duas o honravam: `authorize` vira no-op ✅, o `GET /me` anuncia o **catálogo inteiro** (`list-user-permissions.ts:35`) ✅, e a `approval-policy` (`approval-policy.ts:27`) lia `canApprove` do banco cru e recusava ❌. Medido em 09/09 no ambiente local: o `/me` de um usuário do ETL legado devolvia **47 permissões, incluindo `payable:approve`**; o banco lhe dava **uma**, e não era essa — ele tomava uma recusa que **contradizia o que o `/me` acabara de lhe dizer**, depois de o `authorize` no-op o deixar entrar até o fundo. Aplicado como **decorator** (`withRbacBypass`) composto num ponto só, no composition root do `financial`; o `server.ts` traduz o modo num booleano e o `financial` não passa a conhecer `RbacMode`. ⚠️ Alcança **só o ato de aprovar** (`depsForApprove`): o mesmo port responde a outra pergunta em `saveDocument`/`submitDraft` — se o `approverRef` **indicado** tem alçada —, e essa é **roteamento**, segue enforçada. Compor no `deps` compartilhado, que é o caminho óbvio, faria a indicação **gravar** `approverRef` de quem não é aprovador, e a linha **sobrevive ao religar** — dano que o #634 não desfaz. Continuam valendo sob bypass: `approver-not-found` para usuário inexistente (o bypass afrouxa **permissão**, nunca a existência do sujeito), o **teto** de quem tem papel com alçada (#299/#609) e o `list` de candidatos da cascata (`escalate` é **roteamento de negócio**, não controle de acesso). ⚠️ **Custo aceito, e é o principal: sob bypass todo autenticado aprova qualquer valor** — quem não tem papel aprovador tem teto `null`, e `null` é **SEM TETO** pela regra binária do #299 (`user-read.drizzle.ts:42` + `approval-policy.ts:28-31`), então "o teto continua valendo" **não protege** a população que esta decisão libera. Reabre, enquanto o bypass durar, o buraco que o #609 fechou — e **vale em produção** (linha marcada `← religar` no `server.ts`, risco assumido por escrito no #634). **Gatilho de reversão: o #634 — e ele custa DUAS linhas, não zero:** são duas marcas `← religar`, o `rbacMode` fixado e o `rbacBypass: true`; apagar só a primeira religa a rota e o `/me` e **deixa a policy do domínio afrouxada**, sem que erro de tipo, teste ou lint acusem. **Alternativa recusada** pelo dono: consertar o `/me` subtraindo `payable:approve` do catálogo — sob bypass a promessa é *"todo autenticado é super-usuário"*, e uma regra de domínio que segue cobrando permissão é exceção à promessa, não correção dela. ⚠️ **Não alcança** outras policies de domínio que leiam permissão do banco; hoje esta é a única identificada | Accepted | 2026-09-09 | | [0070](./0070-supplier-individual-cpf-supersedes-0031-partial.md) | **Fornecedor pessoa física: o documento do Fornecedor passa a ser CPF ou CNPJ** — `supersedes` **parcial** de [0031](./0031-partners-registry-module.md) na chave de `par_suppliers` (`cnpj` → `document`) e no campo sensível da edição; `Financier` e `Act` seguem só com CNPJ. A identidade é **union discriminada** no domínio (`individual` × `company`): na PF, razão social e nome fantasia **não existem no tipo**. O tipo de pessoa é **derivado** do documento, nunca gravado — um campo próprio permitiria "PF com CNPJ". `NULL` só nas bordas (coluna e JSON), amarrado ao documento por `CHECK ((CHAR_LENGTH(document) = 11) = (corporate_name IS NULL))`. **Alternativa recusada: valor sentinela** (`-1`, `00`) no lugar de `null` — `null` em JS é primitivo fixo e faz ida e volta exata no `mysql2` e no JSON; o sentinela seria a string `"-1"`, passaria pelo `CHECK` e vazaria para busca, CSV e tela. PF com razão social preenchida é **recusada** (422), não ignorada; branco conta como ausente. HTTP v1 **aditivo**: `document` + `personType` (`PF`/`PJ`), `cnpj` mantido como alias deprecated por um ciclo na entrada e na resposta. ⚠️ PJ sem `corporateName` passa de 400 (Zod) a 422 (domínio). ETL: CPF na coluna legada `cnpj` deixa de ir para quarentena e entra como PF, com os nomes forçados pelo `NOT NULL` legado descartados | Accepted | 2026-10-01 | | [0071](./0071-s3-dev-silo-fork-supersedes-0019-partial.md) | **O S3 de dev/teste passa a ser o fork Silo** — `supersedes` **parcial** de [0019](./0019-document-storage-s3-with-minio-dev.md), só na **fonte da imagem**: a MinIO apagou `minio/minio` e `minio/mc` do Docker Hub em 2026-09-11, o espelho `quay.io/minio` exige login, e o `integration` falha todo dia na `main` desde 2026-09-12 ([#1026](https://github.com/ERP-Bem-Comum/core-api/issues/1026)). O compose passa a `pgsty/silo` + `pgsty/mc`, pinados por digest — fork **mantido**, **AGPLv3** como o MinIO, e **drop-in**: mesmas envs `MINIO_*`, rotas `/minio/*`, API S3. Escolhido entre seis hipóteses testadas em container isolado contra as quatro suítes reais do CI: Bitnami legacy (congelada, sem patches, não drop-in), MinIO compilado do fonte (upstream arquivado — viraríamos mantenedores de segurança), RustFS e SeaweedFS (Apache-2.0, passam no S3, mas exigem refazer serviço e bootstrap). ⚠️ Custo aceito: **bus factor baixo** (um mantenedor principal). Gatilho de saída: RustFS, com ADR próprio. Produção (AWS S3) não muda | Accepted | 2026-10-01 | +| [0072](./0072-oxc-lint-supersedes-inquiry-0029-and-0067-d2.md) | **O lint passa ao `oxlint` type-aware e o TypeScript 7 entra sem side-by-side** — supersede a decisão de linter da [Inquiry-0029](../../inquiries/0029-linter-type-aware-sob-typescript-7.md) e o **D2** de [0067](./0067-typescript-7-side-by-side-supersedes-0009-language.md) (o alias do TS 6), implementando enfim o D1 e o D3 dele. A 0029 escolheu ESLint + `--cache` pela velocidade; medido em 05/10, **o `--cache` dá falso verde** (mudar só o tipo de retorno em `a.ts` deixa `no-floating-promises` em `b.ts` passar com cache e falhar sem — a doc do `typescript-eslint` confirma que o cache não cobre dependência entre arquivos) e **nunca é restaurado no CI** (`Cache not found`; lint de 39–62 s). O `.oxlintrc.json` é **paridade 1:1 gerada** do `eslint --print-config`: 154 das 158 regras com as mesmas opções e exceções, categorias desligadas. As 4 restantes: `no-octal` o `tsc` recusa (`TS1121`); `class`, `naming-convention` e `member-ordering` viram gate de AST (`tests/cleanup/lint-gaps.test.ts`) sobre a API `unstable/` do TS 7. Provado por diferencial (222/224 violações no mesmo lugar), 36/36 sondas de opção, 46/46 nomes idênticos ao ESLint. `typescript` 7.0.2 **exato**; saem ESLint, `typescript-eslint`, `eslint-config-prettier` e `native-preview`; o Prettier fica. Lint 2,3 s. ⚠️ Custos: gates sobre API `unstable/`; três regras portadas são `nursery`; a correção de `.meta({ example })` → `examples` muda o OpenAPI | Accepted | 2026-10-05 | +| [0073](./0073-nvmrc-single-source-supersedes-0058-partial.md) | **O `.nvmrc` é a fonte única da versão do runtime** — `supersedes` **parcial** de [0058](./0058-runtime-tracks-recommended-lts.md): só a §2 (quais pontos declaram a versão) e a §4 (o que o gate cobra); a §1 (acompanhar o LTS recomendado) e a §3 (troca por inquiry) seguem vigentes. O 0058 resolveu o problema certo e **errou a lista**: `.nvmrc` e `devEngines.runtime` já existiam em 05/08/2026 e ficaram fora da tabela da §2 — eram justamente os dois com versão **exata**, enquanto os três listados eram faixa, tag de minor e major solto. Resultado medido em 06/10: dev e `devEngines` em `24.16.0`, `FROM` e `LABEL` em `24.15`, quatro workflows em `'24'` (presos a nenhum dos dois), **com o gate verde** — porque ele cobrava só o major e a divergência era de minor, o que o próprio [0058:41](./0058-runtime-tracks-recommended-lts.md) registrara como "observação, não norma". O `.nvmrc` passa a ser fonte porque é o único arquivo que o `fnm` (no `cd`) e o `actions/setup-node` (`node-version-file`) leem **sem tradução humana**; as 6 derivadas são conferidas por gate na granularidade que cada alvo permite (exata no `devEngines`; `major.minor` no `FROM`/`LABEL`, que é o que a tag oficial publica; major em `engines.node`/`@types/node`). Workflow **MUST NOT** declarar `node-version` em forma alguma — major solto flutua sem diff, valor exato cria 4 cópias novas (alternativa B, rejeitada). ⚠️ Custos: `.nvmrc` ausente derruba todo workflow de uma vez; Dockerfile e `devEngines` ainda **escrevem** o número (conferido, não gerado — gerar exige resolver o digest, que pede rede: alternativa C, reaberta quando houver resolução offline). A pendência de CI agendado do 0058 §4 **continua aberta**: gate offline aprova repositório coerente numa versão EOL | Accepted | 2026-10-06 | | [0074](./0074-hurl-replaces-bruno-http-edge-suite.md) | **Hurl substitui o Bruno como suíte da borda HTTP** — `supersedes` [0034](./0034-adopt-bruno-api-client-cli.md) e [0038](./0038-bruno-cli-mandatory-and-bru-authoring.md). `api-collections/` (266 `.bru`) **removida** e `@usebruno/cli` fora do manifesto (**−317 pacotes**, com as duas exceções de supply-chain que ele exigia). O gatilho foi o critério do próprio 0038: quando o runner único caiu em 05/10, os 266 arquivos viraram a "cobertura ilusória" que ele proibia. Hurl encadeia token com `[Captures]` nativo (sem JavaScript), aceita segredo por `--variables-file` (arquivo nasce sem credencial), dá `--report-junit` para CI e tem `hurlfmt --check` como gate de sintaxe sem servidor. Convertidos **241 requests em 23 arquivos**. ⚠️ Limites medidos: `[Captures]` **não cruzam arquivos**; a suíte exige o **seed E2E** (8 perfis) e hoje roda 82/241; `--jobs 1` obrigatório (rate limit de login compartilhado + `--test` paraleliza por default). O `.http` (`ck http`, IDE) é visualização, **não** gate. Registro histórico não reescrito (ADR-0057 §5) | Accepted | 2026-10-06 | ### Notas de numeração diff --git a/handbook/decisions/ADR-0006.yaml b/handbook/decisions/ADR-0006.yaml index 3be479837..1d9b54e62 100644 --- a/handbook/decisions/ADR-0006.yaml +++ b/handbook/decisions/ADR-0006.yaml @@ -177,7 +177,7 @@ claims: source_lines: [79, 103, 150] applies_to: - 'src/modules/*/**/*.ts' - - 'eslint.config.js' + - '.oxlintrc.json' reality: verdict: partial holds_in: @@ -189,7 +189,7 @@ claims: - 'ausência: nenhuma rule em .semgrep/rules.yml sobre fronteira de módulo' - 'nota: a invariante está de pé por disciplina, exatamente no ponto onde o ADR previu que disciplina não bastaria' verify: | - grep -n "no-cross-context\|public-api" eslint.config.js .semgrep/rules.yml + grep -n "no-cross-context\|public-api" .oxlintrc.json .semgrep/rules.yml testability: verdict: testable layer: static diff --git a/handbook/decisions/ADR-0009.yaml b/handbook/decisions/ADR-0009.yaml index 711737262..1fed5882c 100644 --- a/handbook/decisions/ADR-0009.yaml +++ b/handbook/decisions/ADR-0009.yaml @@ -273,14 +273,14 @@ claims: então o teste falha — a saída HTTP MUST usar `fetch` nativo. expected_error: null blocker: null - enforced_by: ['eslint.config.js'] + enforced_by: ['.oxlintrc.json'] rule: status: proposed disposition: adopt text: | Saída HTTP MUST usar `fetch` nativo. MUST NOT adicionar cliente HTTP de terceiros. rationale: | - A metade proibitiva já é enforçada — o `no-restricted-imports` do `eslint.config.js` barra + A metade proibitiva já é enforçada — o `no-restricted-imports` do `.oxlintrc.json` barra libs do ADR-0011 §4 com a mensagem do ADR no erro. A metade positiva não é exercitada porque ainda não há chamada externa; o primeiro caso real será o microserviço de OCR. diff --git a/handbook/decisions/ADR-0011.yaml b/handbook/decisions/ADR-0011.yaml index d1630983b..5cfb0337e 100644 --- a/handbook/decisions/ADR-0011.yaml +++ b/handbook/decisions/ADR-0011.yaml @@ -174,19 +174,19 @@ claims: kind: prohibition source_lines: ['58-68'] applies_to: - - 'eslint.config.js' + - '.oxlintrc.json' - 'src/**/*.ts' reality: verdict: holds holds_in: [core-api] absent_in: [] evidence: - - 'eslint.config.js:76 — @typescript-eslint/no-restricted-imports com severidade error' - - 'eslint.config.js:82 — entrada de axios com a MENSAGEM DO ADR no erro: "Proibida (ADR-0011 §4): supply chain compromise em mar/2026. Use fetch global."' - - 'eslint.config.js:87 — entrada de request, citando o ADR §4' + - '.oxlintrc.json:287 — eslint/no-restricted-imports com severidade error (portada do @typescript-eslint/no-restricted-imports na troca para o oxlint, ADR-0072)' + - '.oxlintrc.json:292 — entrada de axios com a MENSAGEM DO ADR no erro: "Proibida (ADR-0011 §4): supply chain compromise em mar/2026. Use fetch global."' + - '.oxlintrc.json:296 — entrada de request, citando o ADR §4' - 'ausência: axios e node-fetch não estão em package.json' verify: | - sed -n '75,95p' eslint.config.js + grep -n -A30 'no-restricted-imports' .oxlintrc.json testability: verdict: testable layer: static @@ -195,7 +195,7 @@ claims: roda no gate. expected_error: null blocker: null - enforced_by: ['eslint.config.js'] + enforced_by: ['.oxlintrc.json'] rule: status: proposed disposition: drop diff --git a/handbook/decisions/ADR-0037.yaml b/handbook/decisions/ADR-0037.yaml index b790e3e89..c254a1a99 100644 --- a/handbook/decisions/ADR-0037.yaml +++ b/handbook/decisions/ADR-0037.yaml @@ -248,7 +248,7 @@ claims: `req`, `reply`, prompt), então o teste falha — a camada MUST ser agnóstica de UX. expected_error: null blocker: null - enforced_by: ['eslint.config.js'] + enforced_by: [] rule: status: proposed disposition: drop diff --git a/handbook/decisions/ADR-0045.yaml b/handbook/decisions/ADR-0045.yaml index ba29da777..3b8f9dd00 100644 --- a/handbook/decisions/ADR-0045.yaml +++ b/handbook/decisions/ADR-0045.yaml @@ -132,7 +132,11 @@ claims: teste falha. Dado `src/workers/`, a ligação entre dois módulos MUST estar aqui, não dentro deles. expected_error: null blocker: null - enforced_by: ['eslint.config.js', 'tests/workers/supplier-view-projection/delivery.test.ts'] + enforced_by: + [ + 'tests/cleanup/module-boundary.test.ts', + 'tests/workers/supplier-view-projection/delivery.test.ts', + ] rule: status: proposed disposition: adopt diff --git a/handbook/decisions/ADR-0047.yaml b/handbook/decisions/ADR-0047.yaml index 1b9cd9c8b..a75c988bc 100644 --- a/handbook/decisions/ADR-0047.yaml +++ b/handbook/decisions/ADR-0047.yaml @@ -130,7 +130,7 @@ claims: diretamente, então o teste falha. O produtor MUST conhecer apenas o próprio outbox. expected_error: null blocker: null - enforced_by: ['eslint.config.js'] + enforced_by: [] rule: status: proposed disposition: adopt diff --git a/handbook/decisions/ADR-0051.yaml b/handbook/decisions/ADR-0051.yaml index 987bff3b6..8649d7242 100644 --- a/handbook/decisions/ADR-0051.yaml +++ b/handbook/decisions/ADR-0051.yaml @@ -129,7 +129,7 @@ claims: `budget-plans/public-api`. expected_error: null blocker: null - enforced_by: ['eslint.config.js'] + enforced_by: ['tests/cleanup/module-boundary.test.ts'] rule: status: proposed disposition: drop diff --git a/handbook/decisions/SCHEMA.md b/handbook/decisions/SCHEMA.md index 8333134c3..584253672 100644 --- a/handbook/decisions/SCHEMA.md +++ b/handbook/decisions/SCHEMA.md @@ -114,7 +114,7 @@ módulo aceita `readerUrl` opcional" — e esse sim vira RED estático. ### `enforced_by` — o que já é mecânico não vira texto -Regra 1 do `context/INDEX.md`: se teste, `eslint`, `tsc`, `semgrep` ou hook já garantem, a +Regra 1 do `context/INDEX.md`: se teste, `oxlint`, `tsc`, `semgrep` ou hook já garantem, a alegação **não** precisa virar diretiva escrita — a verificação é a documentação. `enforced_by` lista o caminho desses mecanismos, e o gate confere que cada um existe de fato no repo. diff --git a/handbook/inquiries/0029-linter-type-aware-sob-typescript-7.md b/handbook/inquiries/0029-linter-type-aware-sob-typescript-7.md index 645df4992..ee853ade1 100644 --- a/handbook/inquiries/0029-linter-type-aware-sob-typescript-7.md +++ b/handbook/inquiries/0029-linter-type-aware-sob-typescript-7.md @@ -1,16 +1,24 @@ --- inquiry: 0029 title: "Linter type-aware sob TypeScript 7 — oxlint/tsgolint · Biome · ESLint pinado" -state: decided +state: superseded opened: 2026-08-06 decided: 2026-08-06 -last_reviewed: 2026-08-06 +last_reviewed: 2026-10-05 --- [← Voltar ao Índice de Inquiries](./INDEX.md) # Inquiry-0029: Linter type-aware sob TypeScript 7 — oxlint/tsgolint · Biome · ESLint pinado +> ## ⚠️ Superseded pelo [ADR-0072](../architecture/adr/0072-oxc-lint-supersedes-inquiry-0029-and-0067-d2.md) (2026-10-05) +> +> A decisão de §6 (ESLint + `--cache`) caiu por uma premissa que esta inquiry mediu pela metade: +> o `--cache` é rápido, mas **dá falso verde** em regra type-aware — guarda o resultado por +> arquivo, e o tipo vem de outro (sonda reproduzida e documentação do `typescript-eslint` no +> ADR) — e **nunca foi restaurado no CI** (`Cache not found`, lint de 39–62 s). As medições +> abaixo seguem válidas como registro de 2026-08-06; o que deixou de valer é a conclusão. + - **Opened by:** Gabriel Aderaldo - **Asked to:** IA externa (texto recebido) + verificação em fontes primárias + medição local - **Impact:** [ADR-0009](../architecture/adr/0009-node-24-typescript-6-with-7-roadmap.md) · `eslint.config.js` (342 linhas, 45 regras, 8 acopladas a ADR) · gate de qualidade diff --git a/handbook/inquiries/INDEX.md b/handbook/inquiries/INDEX.md index 46581be2d..3a29932f0 100644 --- a/handbook/inquiries/INDEX.md +++ b/handbook/inquiries/INDEX.md @@ -11,9 +11,9 @@ | :--- | ---: | :--- | | `open` | 8 | quem trabalha nela | | `blocked` | 5 | terceiro (banca, upstream, P.O.) | -| `decided` | 20 | ninguém — fechada | +| `decided` | 19 | ninguém — fechada | | `deferred` | 3 | o gatilho declarado | -| `superseded` | 1 | — | +| `superseded` | 2 | — | Total: **37**. @@ -66,7 +66,6 @@ Total: **37**. | [0022](./0022-jobs-anti-pattern-essential-vs-accidental.md) | Inquiry 0022 — `src/jobs/`/auto-expire: anti-pattern ou complexidade essencial? | | | | [0023](./0023-typescript-7-native-spike.md) | TypeScript 7 nativo — spike medido e diagnóstico de lentidão do `core-api` | 2026-07-31 | 2026-07-31 | | [0024](./0024-adr-format-for-llm-agents.md) | Inquiry 0024 — ADR como contexto de agente: o que o campo convergiu, e onde estamos fora | | | -| [0029](./0029-linter-type-aware-sob-typescript-7.md) | Linter type-aware sob TypeScript 7 — oxlint/tsgolint · Biome · ESLint pinado | 2026-08-06 | 2026-08-06 | | [0033](./0033-cnab-multipag-bisseccao-validador.md) | O que o Bradesco realmente exige — bisseção de 18 remessas contra o Validador Universal | 2026-08-25 | | | [0034](./0034-in-memory-fora-de-local-custo-na-piramide.md) | Eliminar o in-memory fora de LOCAL — o custo na pirâmide de testes, e por que a estimativa de 179 arquivos estava errada | 2026-08-31 | | | [0037](./0037-laudo-multipag-pix-e-validador-cego.md) | O laudo do Multipag Pix — os 6 pontos confirmados, e a descoberta de que o Validador Universal é cego para a modalidade | 2026-09-02 | | @@ -88,6 +87,7 @@ Total: **37**. | # | Título | Aberta | Decidida | | :--- | :--- | :--- | :--- | | [0008](./0008-postgres-driver-pg-vs-postgres.md) | Driver Postgres — `pg` vs `postgres` (porsager) | 2026-04-28 | 2026-04-28 | +| [0029](./0029-linter-type-aware-sob-typescript-7.md) | Linter type-aware sob TypeScript 7 — oxlint/tsgolint · Biome · ESLint pinado | 2026-08-06 | 2026-08-06 | --- diff --git a/handbook/inquiries/PERGUNTAS-EM-ABERTO.md b/handbook/inquiries/PERGUNTAS-EM-ABERTO.md index 5b258a53a..a693d672c 100644 --- a/handbook/inquiries/PERGUNTAS-EM-ABERTO.md +++ b/handbook/inquiries/PERGUNTAS-EM-ABERTO.md @@ -14,7 +14,7 @@ - **Inquiries cobertas:** 13 de 37 — [0011](./0011-auditoria-fiscal-cross-periodo.md) · [0012](./0012-bff-managed-api-gateway-vs-fastify.md) · [0014](./0014-schema-legado-vs-modelo-alvo.md) · [0015](./0015-charset-drizzle-roadmap.md) · [0019](./0019-hard-delete-tripwire-sem-superficie.md) · [0026](./0026-async-human-in-the-loop-and-drizzle-1-0.md) · [0027](./0027-teses-orfas-de-branches-contaminadas.md) · [0028](./0028-edd-da-po-melhorias-m1-m4-e-relatorios-nibo.md) · [0030](./0030-deadman-switch-nunca-vigiou.md) · [0031](./0031-deadlock-na-reserva-atomica-de-remessa.md) · [0032](./0032-titulo-remetido-fronteira-do-agregado.md) · [0035](./0035-norma-de-migration-e-proibicao-de-odku.md) · [0036](./0036-onde-a-documentacao-deve-viver.md) - **Total de perguntas em aberto:** **59** -As demais 24 estão `decided` (20), `deferred` (3, com gatilho declarado) ou `superseded` (1) — nenhuma +As demais 24 estão `decided` (19), `deferred` (3, com gatilho declarado) ou `superseded` (2) — nenhuma espera resposta de alguém. Ver [`INDEX.md`](./INDEX.md). diff --git a/package.json b/package.json index 18492f100..253677d4c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "devEngines": { "runtime": { "name": "node", - "version": "24.16.0", + "version": "24.21.0", "onFail": "download" } }, @@ -27,58 +27,58 @@ "#tests/*": "./tests/*" }, "scripts": { - "preinstall": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/only-allow-pnpm.ts", + "preinstall": "node --enable-source-maps scripts/ci/only-allow-pnpm.ts", "format": "prettier --write .", "format:check": "prettier --check .", "typecheck": "tsc --noEmit", - "lint": "eslint . --cache", - "lint:fix": "eslint . --fix --cache", - "docs:inventory": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/handbook/inventory.ts", - "docs:index": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/handbook/inquiry-index.ts", - "docs:refs": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/handbook/refs.ts", - "docs:links": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/handbook/link-scan.ts", - "wiki:sync": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/handbook/wiki-sync.ts", - "cnab:index": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/financial/cnab-manual-index.ts", - "logbook": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/claude/logbook.ts", - "release:notes": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/release-notes.ts", + "lint": "oxlint", + "lint:fix": "oxlint --fix", + "docs:inventory": "node --enable-source-maps scripts/handbook/inventory.ts", + "docs:index": "node --enable-source-maps scripts/handbook/inquiry-index.ts", + "docs:refs": "node --enable-source-maps scripts/handbook/refs.ts", + "docs:links": "node --enable-source-maps scripts/handbook/link-scan.ts", + "wiki:sync": "node --enable-source-maps scripts/handbook/wiki-sync.ts", + "cnab:index": "node --enable-source-maps scripts/financial/cnab-manual-index.ts", + "logbook": "node --enable-source-maps scripts/claude/logbook.ts", + "release:notes": "node --enable-source-maps scripts/ci/release-notes.ts", "audit": "pnpm audit --prod --audit-level=high", "audit:all": "pnpm audit --audit-level=high", - "test": "node --test --test-concurrency=6 --experimental-strip-types --enable-source-maps --no-warnings 'tests/**/*.test.ts'", - "test:integration": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts contracts", - "worker:outbox": "node --experimental-strip-types --enable-source-maps --no-warnings src/modules/contracts/worker/run.ts", - "worker:outbox:partners": "node --experimental-strip-types --enable-source-maps --no-warnings src/modules/partners/worker/run.ts", - "worker:supplier-projection": "node --experimental-strip-types --enable-source-maps --no-warnings src/workers/supplier-view-projection/run.ts", - "worker:payable-projection": "node --experimental-strip-types --enable-source-maps --no-warnings src/workers/payable-view-projection/run.ts", - "worker:contract-count-projection": "node --experimental-strip-types --enable-source-maps --no-warnings src/workers/contract-count-projection/run.ts", - "worker:email-dispatch": "node --experimental-strip-types --enable-source-maps --no-warnings src/workers/email-dispatch/run.ts", - "job:contracts:sweep": "node --experimental-strip-types --enable-source-maps --no-warnings src/jobs/contracts/sweeper/run.ts", - "job:financial:supplier-view-backfill": "node --experimental-strip-types --enable-source-maps --no-warnings src/jobs/financial/supplier-view-backfill/run.ts", - "job:financial:payable-view-backfill": "node --experimental-strip-types --enable-source-maps --no-warnings src/jobs/financial/payable-view-backfill/run.ts", - "job:partners:contract-count-backfill": "node --experimental-strip-types --enable-source-maps --no-warnings src/jobs/partners/contract-count-backfill/run.ts", - "job:outbox-sweeper": "node --experimental-strip-types --enable-source-maps --no-warnings src/jobs/shared/outbox-sweeper/run.ts", - "job:migrate": "node --experimental-strip-types --enable-source-maps --no-warnings src/jobs/migrate/run.ts", - "job:auth:sync-permissions": "node --experimental-strip-types --enable-source-maps --no-warnings src/jobs/auth/sync-permissions/run.ts", - "secrets:setup": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/setup/secrets.ts", - "test:integration:infra": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts infra", - "test:integration:notifications": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts notifications", - "test:integration:storage": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts storage", - "test:integration:auth": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts auth", - "test:integration:partners": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts partners", - "test:integration:programs": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts programs", - "test:integration:budget-plans": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts budget-plans", - "test:integration:financial": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts financial", - "test:integration:etl": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts etl", - "test:integration:etl:orchestrate": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts etl:orchestrate", - "test:integration:etl:contracts": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts etl:contracts", - "test:integration:etl:financial": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts etl:financial", - "etl:check-duplicates": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/etl/diagnostics/check-duplicates.ts", - "serve": "node --experimental-strip-types --enable-source-maps --no-warnings src/server.ts", + "test": "node --enable-source-maps --test --test-concurrency=6 'tests/**/*.test.ts'", + "test:integration": "node --enable-source-maps scripts/ci/test-integration.ts contracts", + "worker:outbox": "node --enable-source-maps src/modules/contracts/worker/run.ts", + "worker:outbox:partners": "node --enable-source-maps src/modules/partners/worker/run.ts", + "worker:supplier-projection": "node --enable-source-maps src/workers/supplier-view-projection/run.ts", + "worker:payable-projection": "node --enable-source-maps src/workers/payable-view-projection/run.ts", + "worker:contract-count-projection": "node --enable-source-maps src/workers/contract-count-projection/run.ts", + "worker:email-dispatch": "node --enable-source-maps src/workers/email-dispatch/run.ts", + "job:contracts:sweep": "node --enable-source-maps src/jobs/contracts/sweeper/run.ts", + "job:financial:supplier-view-backfill": "node --enable-source-maps src/jobs/financial/supplier-view-backfill/run.ts", + "job:financial:payable-view-backfill": "node --enable-source-maps src/jobs/financial/payable-view-backfill/run.ts", + "job:partners:contract-count-backfill": "node --enable-source-maps src/jobs/partners/contract-count-backfill/run.ts", + "job:outbox-sweeper": "node --enable-source-maps src/jobs/shared/outbox-sweeper/run.ts", + "job:migrate": "node --enable-source-maps src/jobs/migrate/run.ts", + "job:auth:sync-permissions": "node --enable-source-maps src/jobs/auth/sync-permissions/run.ts", + "secrets:setup": "node --enable-source-maps scripts/setup/secrets.ts", + "test:integration:infra": "node --enable-source-maps scripts/ci/test-integration.ts infra", + "test:integration:notifications": "node --enable-source-maps scripts/ci/test-integration.ts notifications", + "test:integration:storage": "node --enable-source-maps scripts/ci/test-integration.ts storage", + "test:integration:auth": "node --enable-source-maps scripts/ci/test-integration.ts auth", + "test:integration:partners": "node --enable-source-maps scripts/ci/test-integration.ts partners", + "test:integration:programs": "node --enable-source-maps scripts/ci/test-integration.ts programs", + "test:integration:budget-plans": "node --enable-source-maps scripts/ci/test-integration.ts budget-plans", + "test:integration:financial": "node --enable-source-maps scripts/ci/test-integration.ts financial", + "test:integration:etl": "node --enable-source-maps scripts/ci/test-integration.ts etl", + "test:integration:etl:orchestrate": "node --enable-source-maps scripts/ci/test-integration.ts etl:orchestrate", + "test:integration:etl:contracts": "node --enable-source-maps scripts/ci/test-integration.ts etl:contracts", + "test:integration:etl:financial": "node --enable-source-maps scripts/ci/test-integration.ts etl:financial", + "etl:check-duplicates": "node --enable-source-maps scripts/etl/diagnostics/check-duplicates.ts", + "serve": "node --enable-source-maps src/server.ts", "dev": "overmind start", "test:e2e:auth": "bash scripts/e2e/auth.sh", "test:e2e:contracts": "bash scripts/e2e/contracts.sh", "test:e2e:collaborators": "bash scripts/e2e/collaborators.sh", - "test:integration:photo": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts photo", - "test:integration:logo": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/ci/test-integration.ts logo", + "test:integration:photo": "node --enable-source-maps scripts/ci/test-integration.ts photo", + "test:integration:logo": "node --enable-source-maps scripts/ci/test-integration.ts logo", "db:generate": "drizzle-kit generate --config db/drizzle/contracts.ts", "db:generate:auth": "drizzle-kit generate --config db/drizzle/auth.ts", "db:generate:partners": "drizzle-kit generate --config db/drizzle/partners.ts", @@ -86,22 +86,19 @@ "db:generate:financial": "drizzle-kit generate --config db/drizzle/financial.ts", "db:generate:notifications": "drizzle-kit generate --config db/drizzle/notifications.ts", "db:generate:budget-plans": "drizzle-kit generate --config db/drizzle/budget-plans.ts", - "db:seed:partners": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/seed/partners.ts", - "seed:admin": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/seed/admin-user.ts", - "financial:projection-health": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/financial/diagnostics/projection-health.ts", - "financial:payee-bank-distribution": "node --experimental-strip-types --enable-source-maps --no-warnings scripts/financial/diagnostics/payee-bank-distribution.ts" + "db:seed:partners": "node --enable-source-maps scripts/seed/partners.ts", + "seed:admin": "node --enable-source-maps scripts/seed/admin-user.ts", + "financial:projection-health": "node --enable-source-maps scripts/financial/diagnostics/projection-health.ts", + "financial:payee-bank-distribution": "node --enable-source-maps scripts/financial/diagnostics/payee-bank-distribution.ts" }, "devDependencies": { - "@eslint/js": "^10.0.1", "@types/node": "^24.0.0", "@types/nodemailer": "^8.0.1", - "@typescript/native-preview": "7.0.0-dev.20260515.1", "drizzle-kit": "^0.31.10", - "eslint": "^10.3.0", - "eslint-config-prettier": "^10.1.8", + "oxlint": "1.86.0", + "oxlint-tsgolint": "7.0.2003", "prettier": "^3.8.3", - "typescript": "^6.0.0", - "typescript-eslint": "^8.59.3", + "typescript": "7.0.2", "yaml": "^2.9.0" }, "dependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 27bed7a05..3ce97ce3d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,39 +73,30 @@ importers: specifier: 5.4.6 version: 5.4.6(zod@4.4.3) devDependencies: - '@eslint/js': - specifier: ^10.0.1 - version: 10.0.1(eslint@10.3.0(supports-color@7.2.0)) '@types/node': specifier: ^24.0.0 version: 24.13.3 '@types/nodemailer': specifier: ^8.0.1 version: 8.0.1 - '@typescript/native-preview': - specifier: 7.0.0-dev.20260515.1 - version: 7.0.0-dev.20260515.1 drizzle-kit: specifier: ^0.31.10 version: 0.31.10 - eslint: - specifier: ^10.3.0 - version: 10.3.0(supports-color@7.2.0) - eslint-config-prettier: - specifier: ^10.1.8 - version: 10.1.8(eslint@10.3.0(supports-color@7.2.0)) node: - specifier: runtime:24.16.0 - version: runtime:24.16.0 + specifier: runtime:24.21.0 + version: runtime:24.21.0 + oxlint: + specifier: 1.86.0 + version: 1.86.0(oxlint-tsgolint@7.0.2003) + oxlint-tsgolint: + specifier: 7.0.2003 + version: 7.0.2003 prettier: specifier: ^3.8.3 version: 3.8.3 typescript: - specifier: ^6.0.0 - version: 6.0.3 - typescript-eslint: - specifier: ^8.59.3 - version: 8.59.3(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + specifier: 7.0.2 + version: 7.0.2 yaml: specifier: ^2.9.0 version: 2.9.0 @@ -402,45 +393,6 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/config-array@0.23.5': - resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/config-helpers@0.5.5': - resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/core@1.2.1': - resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/js@10.0.1': - resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - peerDependencies: - eslint: ^10.0.0 - peerDependenciesMeta: - eslint: - optional: true - - '@eslint/object-schema@3.0.5': - resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/plugin-kit@0.7.1': - resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@fastify/accept-negotiator@2.0.1': resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==} @@ -483,26 +435,6 @@ packages: '@fastify/swagger@9.7.0': resolution: {integrity: sha512-Vp1SC1GC2Hrkd3faFILv86BzUNyFz5N4/xdExqtCgkGASOzn/x+eMe4qXIGq7cdT6wif/P/oa6r1Ruqx19paZA==} - '@humanfs/core@0.19.2': - resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.8': - resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} - engines: {node: '>=18.18.0'} - - '@humanfs/types@0.15.0': - resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} - '@lukeed/ms@2.0.2': resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} @@ -510,6 +442,158 @@ packages: '@nodable/entities@2.1.0': resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==} + '@oxlint-tsgolint/darwin-arm64@7.0.2003': + resolution: {integrity: sha512-TgV33rXr6ueXBwvc+0nssUkTBSXHJxv77I8p4RCjDjCnvexHtmoPiudVRDfj3S3puMDdeQdHW6jdUgUPy3nr/w==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@7.0.2003': + resolution: {integrity: sha512-hY3FMAjIaPDdK3FNxyRXRfHPOFeoBn6dmnLyKZgQ2IbTTD1adXhl6PfCIqHhCPvurigv7nf7mYuWZZ19MmJzmg==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@7.0.2003': + resolution: {integrity: sha512-eAET4JpyfBbg8SO0K74o4R55tEjVC292pIyxGOw2XGf8x/HrPNyxwQ478jMYOM54FIVOHc8sJ6VRHxluy6lucw==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@7.0.2003': + resolution: {integrity: sha512-GXdyO/XyqDJ3s/llR/oOktLsYNjZtWSQBy0JMc+/0gsNPvTcseKPhn9c6KcFyWmnr6H4vljYALbujonqSzzEGw==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@7.0.2003': + resolution: {integrity: sha512-TWauXnPfet0VgpmrstoAK58eJ4gbuwPUuUTQmYQAzE/RG1CpnxXtrKUJULf6lyerPE4KN3gM+DflIA1hOH+38Q==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@7.0.2003': + resolution: {integrity: sha512-DoRmfe7j8VqNlukp8liRVW45GQDhzRccNenjD/pdzelgtffW47pCMd1xbJLkPaPbKnTwID3onn3VZlL7JbebEA==} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.86.0': + resolution: {integrity: sha512-63Ozq6yQn79B2UxjSZ9huJmI+3/ZPI7hxAe6TyFh9oQL447KnIMoffOdtog+h4KILbQr61ZRvFPW5gYbAUgpgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.86.0': + resolution: {integrity: sha512-JdLGp44phbf/0Du236Dt06yfK6Qw8xhTFeDvHJdjk167r59OfP5+BL31mgMYVnswS8e+j6tFuccSY0QDC/lgCw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.86.0': + resolution: {integrity: sha512-h+vkOr4ik6KLFCXdtNVEj9xfnXfNUpPoF95QjORdhiSpLqQePzcN81kGHjAgsw1/G7WBWK5KsFKbI2UK8EyzMA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.86.0': + resolution: {integrity: sha512-wbglRuH5eKsp68keTOOeKDHU1astWSRYFsh2bwKQTAPb44rD6kVgzYI1ZrAJRTc6/7PH55ZXFCQx0rQXrj0UdA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.86.0': + resolution: {integrity: sha512-yTQ7QSOlNfQsrfbeVweTiegQJLKPjUKgXp/JpGiFzza6Hp29S9LAoH77TM3L0RjK39iQRZB5lk5iq2t241Mfag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.86.0': + resolution: {integrity: sha512-QYJGy0E2Tv8GAayyBIeHFTwQb8u+zrvrOYrifuDGZDr9ad5XsDhiMScpZymhS9fwWjqrgG2ZUwT4vpY0ISqp4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.86.0': + resolution: {integrity: sha512-oQo83CafLT8p3qh0AGkN0tSUIh5Hm63mfDsoqUmBQUBnSZLNBAbyRaAftfGY5a0cKZ34dG6jAtMYgcY5kZqt5g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.86.0': + resolution: {integrity: sha512-EM6wy5c2UM12qPb7iDJDBAZvHyfEJGH6iosFgaFiaRZpirz99//EHcUyD2KAskz07x1TULuiAtsojf4nvWWksw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.86.0': + resolution: {integrity: sha512-vCiUQb9ZNzZalxYRU4mlZwUqT5f+c7Sk9DwUOplWHtik3Dl4r3DVnUB5tBeRuTAUe6MUVCTylNW7TkkN8J1oTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.86.0': + resolution: {integrity: sha512-T0AzE89yjTYmOhB/hb6i+rWTpe2mYt+xiguBhs9DEPIJTZ1H8OYqsE4VxJHHw2Wlq8Mztwvp2P8NV87ZOuhPRw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.86.0': + resolution: {integrity: sha512-s49r0K2f5Oc7fJoMUO1WkdhYK5Ob79uYRwWQ/XNoGysaOq3RUGmGUbHSuXiyydTTA+P9xEdDEnnlZVXiaSJhlw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.86.0': + resolution: {integrity: sha512-JIa2tYR8ayb3nvcMSZzn4O/xTy13S7MHllu7cBD+B5qDQXXSRy/Vwk1aNpiTjZmBJGwKCHOy/NvnY5Xcwg27uQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.86.0': + resolution: {integrity: sha512-XwXMyBsAuWZkhZU74UNcfQKr1CJJAKnS80dZzOnjl6A4fKcf91IjFgidegQugMHA5SN8ulwDXEDV1yPntjXgQQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.86.0': + resolution: {integrity: sha512-C1WjukSyMnr66b+w1/tV8RFVv6d9v0MzDf4p9IxVXknqgmTHBgZh1pccN1eHzFr0b9Tbb3OXoPsAAdAuHAQfeA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.86.0': + resolution: {integrity: sha512-ap6KLmvC38c6MdYzsIh25cXQupqYvjd37tMNftzrX1DCtkX1Gcf2B+S2B17dD4lKa5c5gJog7DQJyDo82PBKyw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.86.0': + resolution: {integrity: sha512-996Q9w/2GLwNyy5Djs39Jt09aFlr4b7TPhoj7dvo5FHmcyiF68nPz73HNc5UAYKIYeK+M9JzPQ4Wg1r1dXvP6A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.86.0': + resolution: {integrity: sha512-9uTCHYTknNkug5+46ViSoUMuOuGWK61D87QnblU6CXaWKJC630Z7dbls61vdHxya/cR2ycQZ6Mm6ckgF6Cag5g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.86.0': + resolution: {integrity: sha512-a47ga7EKVfMU8rO0i1m3mGwfNRj3FhQyt1d/ukO/E9u1HHF3KpR3tyb3VhuJBGEVPF8uR4exSyHUyx7xbE+uxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.86.0': + resolution: {integrity: sha512-az4dzifCRzES1crwu6pDFXzJTIanrdxXFL9TbiJWrx9LKcuacbR83aPvxDA1LKLNswi/RoqitC7Afcflhu3zBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} @@ -552,140 +636,135 @@ packages: '@stablelib/base64@1.0.1': resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} - '@types/esrecurse@4.3.1': - resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} - - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@24.13.3': resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} '@types/nodemailer@8.0.1': resolution: {integrity: sha512-PxpaInm8V1JQDd4j0ds5HfvWQk8JupS1C0Picb96QJsrrRDjBH+DlK7L4ZdNSqNULhiZRQHc40nLVShaGxXAMw==} - '@typescript-eslint/eslint-plugin@8.59.3': - resolution: {integrity: sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.59.3 - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] - '@typescript-eslint/parser@8.59.3': - resolution: {integrity: sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] - '@typescript-eslint/project-service@8.59.3': - resolution: {integrity: sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] - '@typescript-eslint/scope-manager@8.59.3': - resolution: {integrity: sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] - '@typescript-eslint/tsconfig-utils@8.59.3': - resolution: {integrity: sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] - '@typescript-eslint/type-utils@8.59.3': - resolution: {integrity: sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] - '@typescript-eslint/types@8.59.3': - resolution: {integrity: sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] - '@typescript-eslint/typescript-estree@8.59.3': - resolution: {integrity: sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] - '@typescript-eslint/utils@8.59.3': - resolution: {integrity: sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] - '@typescript-eslint/visitor-keys@8.59.3': - resolution: {integrity: sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260515.1': - resolution: {integrity: sha512-CSvyLkNV0k4Ro0B6nzNdDXFrRD8aa6sDvXSGla2FTmBio+JLKqNuJXq1ppyj42j9pAwdUhDZV/PNdjeHRCBjWQ==} + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} engines: {node: '>=16.20.0'} cpu: [arm64] - os: [darwin] + os: [netbsd] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260515.1': - resolution: {integrity: sha512-QTmm0dpF6CC3hrfudAyVcIvbr1tlBTZ7aGQHIs4PYmi+tnTi2R8jy6gDBCJWlDBSXqjPWlQZrdiOxRBCSlShSA==} + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} engines: {node: '>=16.20.0'} cpu: [x64] - os: [darwin] + os: [netbsd] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260515.1': - resolution: {integrity: sha512-bH/CYrtXURXr3YwqJmlKblQ858wuJ0Qw2VSMvlsWwYZpb8eOd07T2bl1Ba6QxGBuL6EotcDQFd2OYJIj0uvM7A==} + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} engines: {node: '>=16.20.0'} cpu: [arm64] - os: [linux] + os: [openbsd] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260515.1': - resolution: {integrity: sha512-0b+Sxh8JUNMqvw06wfIoh45+G68/ikCW+aRqHGipU2uc9M9dWUKvI3zDyzP1rF6FOJ7xMKMbTbi5b2IvcRqh8g==} + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} engines: {node: '>=16.20.0'} - cpu: [arm] - os: [linux] + cpu: [x64] + os: [openbsd] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260515.1': - resolution: {integrity: sha512-eXgYE6pmkCiEEX7T43TYeF24uiY8+h9mbi9YSvnCoRp4BLgn9b7uJagh4ctSGCNxUy3QSDeoznkWfSVlrvJ5pA==} + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} engines: {node: '>=16.20.0'} cpu: [x64] - os: [linux] + os: [sunos] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260515.1': - resolution: {integrity: sha512-K1YJimcOr3/lFTJyOChT1GnRdfvoHtIvG/qcwHhHpeCBzpGfG7u2aH9MkBgsXqNJHAXHLnQRuON2uYgU8wWDzw==} + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260515.1': - resolution: {integrity: sha512-1cGEWLao/d2+Q3yMvtnDES85sM9HuI6J6PBmrcjwXzsQxnjXJkujStdp+LB6JZfY+VSDi+cpaCZmrKU6sYUfkg==} + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} engines: {node: '>=16.20.0'} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260515.1': - resolution: {integrity: sha512-dWhzJGb9iJfCDsjz/LzPkbMj5XDbFWyZmDyTixcmQPgX5zFOPZ+sAdlCpTZFGEJhA7OqXG+lZyajbJfjYvQorA==} - engines: {node: '>=16.20.0'} - hasBin: true - abstract-logging@2.0.1: resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -694,9 +773,6 @@ packages: ajv: optional: true - ajv@6.15.0: - resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} - ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} @@ -733,10 +809,6 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -746,9 +818,6 @@ packages: supports-color: optional: true - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -865,76 +934,18 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - eslint-config-prettier@10.1.8: - resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - - eslint-scope@9.1.2: - resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@5.0.1: - resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - eslint@10.3.0: - resolution: {integrity: sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - - espree@11.2.0: - resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fast-json-stringify@6.4.0: resolution: {integrity: sha512-ibRCQ0GZKJIQ+P3Et1h0LhPgp3PMTYk0MH8O+kW3lNYsvmaQww5Nn3f1jf73Q0jR1Yz3a1CDP4/NZD3vOajWJQ==} fast-json-stringify@7.0.1: resolution: {integrity: sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==} - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} @@ -980,34 +991,10 @@ packages: fastq@1.20.3: resolution: {integrity: sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==} - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} - find-my-way@9.9.0: resolution: {integrity: sha512-sJsgZ1sQH2UDuowPuMKg8az7Qc8F0jnj+SKkFWU/+T0xcFlgV5skgXOGUqmQzOdmW6ALA7AhJINWx3qFBkbLHA==} engines: {node: '>=20'} - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} - - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1019,10 +1006,6 @@ packages: get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - glob@13.0.6: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} @@ -1046,18 +1029,6 @@ packages: resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} engines: {node: '>=0.10.0'} - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -1065,26 +1036,12 @@ packages: resolution: {integrity: sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==} engines: {node: '>= 10'} - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-schema-ref-resolver@3.0.0: resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==} @@ -1092,29 +1049,12 @@ packages: resolution: {integrity: sha512-HqMnbz0tz2DaEJ3ntsqtx3ezzZyDE7G56A/pPY/NGmrPu76UzsWquOpHFRAf5beTNXoH2LU5cQePVvRli1nchA==} engines: {node: '>=20'} - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - light-my-request@6.6.0: resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -1152,97 +1092,136 @@ packages: resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} engines: {node: '>=8.0.0'} - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - node@runtime:24.16.0: + node@runtime:24.21.0: resolution: type: variations variants: - resolution: archive: tarball - bin: bin/node - integrity: sha256-MN/Y5EMiwnEoE/JeFjwlPK1TvkPgmJB7i1NIvxdKSWg= + bin: + node: bin/node + integrity: sha256-rseyRkr7mfB4wZywbSAdVDvTsxHLoHEoK84bF8l+WLs= type: binary - url: https://nodejs.org/download/release/v24.16.0/node-v24.16.0-aix-ppc64.tar.gz + url: https://nodejs.org/download/release/v24.21.0/node-v24.21.0-aix-ppc64.tar.gz targets: - cpu: ppc64 os: aix - resolution: archive: tarball - bin: bin/node - integrity: sha256-ORidq07rFXBsQkrwrAijBEyeSPfbEqfXf2t6r8fdXfY= + bin: + node: bin/node + integrity: sha256-vtfupTJeEQjzLOUijd1qXw8IpJnuQqp0Qq6lg3AvYFc= type: binary - url: https://nodejs.org/download/release/v24.16.0/node-v24.16.0-darwin-arm64.tar.gz + url: https://nodejs.org/download/release/v24.21.0/node-v24.21.0-darwin-arm64.tar.gz targets: - cpu: arm64 os: darwin - resolution: archive: tarball - bin: bin/node - integrity: sha256-KYtMezy4B2XIcD5CuQMkpOzjtmNJR7iedpw8mAq1UYU= + bin: + node: bin/node + integrity: sha256-FGLLOzBGuBXPjqQ209pFDsGp8R2sflpGsK2lMF1+gJc= type: binary - url: https://nodejs.org/download/release/v24.16.0/node-v24.16.0-darwin-x64.tar.gz + url: https://nodejs.org/download/release/v24.21.0/node-v24.21.0-darwin-x64.tar.gz targets: - cpu: x64 os: darwin - resolution: archive: tarball - bin: bin/node - integrity: sha256-WJ9bbdT8/uTf2nMBOQPJZquqir2T28nUNlRORytPDnQ= + bin: + node: bin/node + integrity: sha256-ckKCw7Q67JmKqVJzgEZbRdIp4CG1gDX19PYwleq/5dU= type: binary - url: https://nodejs.org/download/release/v24.16.0/node-v24.16.0-linux-arm64.tar.gz + url: https://nodejs.org/download/release/v24.21.0/node-v24.21.0-linux-arm64.tar.gz targets: - cpu: arm64 os: linux - resolution: archive: tarball - bin: bin/node - integrity: sha256-JStYIFNNwDBKKFQcmkRDfPpyAufyAiXSjUk5MsWOl6o= + bin: + node: bin/node + integrity: sha256-UcXVMGbukpILYXg7A7Buy5nzZhFg97Fcp36EdBFaZ7w= type: binary - url: https://nodejs.org/download/release/v24.16.0/node-v24.16.0-linux-ppc64le.tar.gz + url: https://nodejs.org/download/release/v24.21.0/node-v24.21.0-linux-ppc64le.tar.gz targets: - cpu: ppc64le os: linux - resolution: archive: tarball - bin: bin/node - integrity: sha256-Vnrwl1s0BVFrmx3cZEKaI+yMWi+mzwE5EmGkzHdOPt0= + bin: + node: bin/node + integrity: sha256-Xy+jdCLgx13jXBaG/lHXgyTywqj+LMI5pB2cAAop2Tg= type: binary - url: https://nodejs.org/download/release/v24.16.0/node-v24.16.0-linux-s390x.tar.gz + url: https://nodejs.org/download/release/v24.21.0/node-v24.21.0-linux-s390x.tar.gz targets: - cpu: s390x os: linux - resolution: archive: tarball - bin: bin/node - integrity: sha256-L69qOH6bYriI4hxU8BJJ+ydTf/7PGELyn0yRnQpZoP8= + bin: + node: bin/node + integrity: sha256-PWNAX8ZaDS0pdsHwvC/Se7C9ByEkaecFqsPwOuWrTJw= type: binary - url: https://nodejs.org/download/release/v24.16.0/node-v24.16.0-linux-x64.tar.gz + url: https://nodejs.org/download/release/v24.21.0/node-v24.21.0-linux-x64-musl.tar.gz + targets: + - cpu: x64 + os: linux + libc: musl + - resolution: + archive: tarball + bin: + node: bin/node + integrity: sha256-bh24fvWLiBnl1UAu/xU2SRsY7djre+5e94l4duiNxf8= + type: binary + url: https://nodejs.org/download/release/v24.21.0/node-v24.21.0-linux-x64.tar.gz targets: - cpu: x64 os: linux - resolution: archive: zip - bin: node.exe - integrity: sha256-FINGEdTGs8BgVOcAdzK5BHTBbgsy85XgW1Wlce9xxtI= - prefix: node-v24.16.0-win-arm64 + bin: + node: node.exe + integrity: sha256-h3mxveHTn41CDjtXqmV7OYka9DTT3kSpGQRM7AZ4WSE= + prefix: node-v24.21.0-win-arm64 type: binary - url: https://nodejs.org/download/release/v24.16.0/node-v24.16.0-win-arm64.zip + url: https://nodejs.org/download/release/v24.21.0/node-v24.21.0-win-arm64.zip targets: - cpu: arm64 os: win32 - resolution: archive: zip - bin: node.exe - integrity: sha256-7aypvVjsjpIDfaxOh31S9rj0MLgcGLV+JktOL7ERzVY= - prefix: node-v24.16.0-win-x64 + bin: + node: node.exe + integrity: sha256-FY92hbRN5R9sDfHRU1JsvNPhvHOajfxgdyHO913p5UE= + prefix: node-v24.21.0-win-x64 type: binary - url: https://nodejs.org/download/release/v24.16.0/node-v24.16.0-win-x64.zip + url: https://nodejs.org/download/release/v24.21.0/node-v24.21.0-win-x64.zip targets: - cpu: x64 os: win32 - version: 24.16.0 + - resolution: + archive: tarball + bin: + node: bin/node + integrity: sha256-MEiwgR4VjKDYZytZyDmGF2PhREkpgNjSmzad/uRXR+Q= + type: binary + url: https://unofficial-builds.nodejs.org/download/release/v24.21.0/node-v24.21.0-linux-arm64-musl.tar.gz + targets: + - cpu: arm64 + os: linux + libc: musl + - resolution: + archive: tarball + bin: + node: bin/node + integrity: sha256-AacT402pjntNKjGR7nyj+T3QOE0aLvI8N5shYe5uEzo= + type: binary + url: https://unofficial-builds.nodejs.org/download/release/v24.21.0/node-v24.21.0-linux-x64-musl.tar.gz + targets: + - cpu: x64 + os: linux + libc: musl + version: 24.21.0 hasBin: true nodemailer@10.0.15: @@ -1256,38 +1235,31 @@ packages: openapi-types@12.1.3: resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + oxlint-tsgolint@7.0.2003: + resolution: {integrity: sha512-VnK4zlqgmgq/7ZcjzCk/WpN8kKFsYGcB85Io9qT3wL4K8Un3RKmJkp793crNETieMusPMKOA4a+Mw2wbteI6TQ==} + hasBin: true - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} + oxlint@1.86.0: + resolution: {integrity: sha512-og0lhgvZfgGF//gOOmZXvtr+GmBbAGEnbEhv/QUg7UW2Wi4wHMnJbnMD+zuHgPdJxdklfgpPupdlAaAySyxrZg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2003' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true path-expression-matcher@1.5.0: resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} engines: {node: '>=14.0.0'} - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - path-scurry@2.0.2: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} - pino-abstract-transport@3.0.0: resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} @@ -1301,10 +1273,6 @@ packages: postal-mime@2.7.4: resolution: {integrity: sha512-0WdnFQYUrPGGTFu1uOqD2s7omwua8xaeYGdO6rb88oD5yJ/4pPHDA4sdWqfD8wQVfCny563n/HQS7zTFft+f/g==} - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - prettier@3.8.3: resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} engines: {node: '>=14'} @@ -1316,10 +1284,6 @@ packages: process-warning@5.1.0: resolution: {integrity: sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==} - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} @@ -1371,11 +1335,6 @@ packages: secure-json-parse@4.1.0: resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} - semver@7.8.0: - resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} - engines: {node: '>=10'} - hasBin: true - semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} @@ -1387,14 +1346,6 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - sonic-boom@4.2.1: resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} @@ -1431,10 +1382,6 @@ packages: resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} engines: {node: '>=20'} - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} - engines: {node: '>=12.0.0'} - toad-cache@3.7.1: resolution: {integrity: sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ==} engines: {node: '>=20'} @@ -1447,12 +1394,6 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - ts-api-utils@2.5.0: - resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -1461,20 +1402,9 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - typescript-eslint@8.59.3: - resolution: {integrity: sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} hasBin: true undici-types@7.18.2: @@ -1488,18 +1418,6 @@ packages: '@napi-rs/canvas': optional: true - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - xml-naming@0.1.0: resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} engines: {node: '>=16.0.0'} @@ -1509,10 +1427,6 @@ packages: engines: {node: '>= 14.6'} hasBin: true - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - zod-openapi@5.4.6: resolution: {integrity: sha512-P2jsOOBAq/6hCwUsMCjUATZ8szkMsV5VAwZENfyxp2Hc/XPJQpVwAgevWZc65xZauCwWB9LAn7zYeiCJFAEL+A==} engines: {node: '>=20'} @@ -1886,40 +1800,6 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.3.0(supports-color@7.2.0))': - dependencies: - eslint: 10.3.0(supports-color@7.2.0) - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.2': {} - - '@eslint/config-array@0.23.5(supports-color@7.2.0)': - dependencies: - '@eslint/object-schema': 3.0.5 - debug: 4.4.3(supports-color@7.2.0) - minimatch: 10.2.5 - transitivePeerDependencies: - - supports-color - - '@eslint/config-helpers@0.5.5': - dependencies: - '@eslint/core': 1.2.1 - - '@eslint/core@1.2.1': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/js@10.0.1(eslint@10.3.0(supports-color@7.2.0))': - optionalDependencies: - eslint: 10.3.0(supports-color@7.2.0) - - '@eslint/object-schema@3.0.5': {} - - '@eslint/plugin-kit@0.7.1': - dependencies: - '@eslint/core': 1.2.1 - levn: 0.4.1 - '@fastify/accept-negotiator@2.0.1': {} '@fastify/ajv-compiler@4.0.6': @@ -1997,25 +1877,84 @@ snapshots: transitivePeerDependencies: - supports-color - '@humanfs/core@0.19.2': - dependencies: - '@humanfs/types': 0.15.0 + '@lukeed/ms@2.0.2': {} - '@humanfs/node@0.16.8': - dependencies: - '@humanfs/core': 0.19.2 - '@humanfs/types': 0.15.0 - '@humanwhocodes/retry': 0.4.3 + '@nodable/entities@2.1.0': {} - '@humanfs/types@0.15.0': {} + '@oxlint-tsgolint/darwin-arm64@7.0.2003': + optional: true - '@humanwhocodes/module-importer@1.0.1': {} + '@oxlint-tsgolint/darwin-x64@7.0.2003': + optional: true - '@humanwhocodes/retry@0.4.3': {} + '@oxlint-tsgolint/linux-arm64@7.0.2003': + optional: true - '@lukeed/ms@2.0.2': {} + '@oxlint-tsgolint/linux-x64@7.0.2003': + optional: true - '@nodable/entities@2.1.0': {} + '@oxlint-tsgolint/win32-arm64@7.0.2003': + optional: true + + '@oxlint-tsgolint/win32-x64@7.0.2003': + optional: true + + '@oxlint/binding-android-arm-eabi@1.86.0': + optional: true + + '@oxlint/binding-android-arm64@1.86.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.86.0': + optional: true + + '@oxlint/binding-darwin-x64@1.86.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.86.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.86.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.86.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.86.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.86.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.86.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.86.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.86.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.86.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.86.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.86.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.86.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.86.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.86.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.86.0': + optional: true '@pinojs/redact@0.4.0': {} @@ -2069,12 +2008,6 @@ snapshots: '@stablelib/base64@1.0.1': {} - '@types/esrecurse@4.3.1': {} - - '@types/estree@1.0.9': {} - - '@types/json-schema@7.0.15': {} - '@types/node@24.13.3': dependencies: undici-types: 7.18.2 @@ -2083,147 +2016,72 @@ snapshots: dependencies: '@types/node': 24.13.3 - '@typescript-eslint/eslint-plugin@8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.3(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.59.3 - '@typescript-eslint/type-utils': 8.59.3(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.3(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.59.3 - eslint: 10.3.0(supports-color@7.2.0) - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true - '@typescript-eslint/parser@8.59.3(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.59.3 - '@typescript-eslint/types': 8.59.3 - '@typescript-eslint/typescript-estree': 8.59.3(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.59.3 - debug: 4.4.3(supports-color@7.2.0) - eslint: 10.3.0(supports-color@7.2.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true - '@typescript-eslint/project-service@8.59.3(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.3(typescript@6.0.3) - '@typescript-eslint/types': 8.59.3 - debug: 4.4.3(supports-color@7.2.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color + '@typescript/typescript-darwin-x64@7.0.2': + optional: true - '@typescript-eslint/scope-manager@8.59.3': - dependencies: - '@typescript-eslint/types': 8.59.3 - '@typescript-eslint/visitor-keys': 8.59.3 + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true - '@typescript-eslint/tsconfig-utils@8.59.3(typescript@6.0.3)': - dependencies: - typescript: 6.0.3 + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true - '@typescript-eslint/type-utils@8.59.3(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@typescript-eslint/types': 8.59.3 - '@typescript-eslint/typescript-estree': 8.59.3(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.3(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - debug: 4.4.3(supports-color@7.2.0) - eslint: 10.3.0(supports-color@7.2.0) - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color + '@typescript/typescript-linux-arm64@7.0.2': + optional: true - '@typescript-eslint/types@8.59.3': {} + '@typescript/typescript-linux-arm@7.0.2': + optional: true - '@typescript-eslint/typescript-estree@8.59.3(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@typescript-eslint/project-service': 8.59.3(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.59.3(typescript@6.0.3) - '@typescript-eslint/types': 8.59.3 - '@typescript-eslint/visitor-keys': 8.59.3 - debug: 4.4.3(supports-color@7.2.0) - minimatch: 10.2.5 - semver: 7.8.0 - tinyglobby: 0.2.16 - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color + '@typescript/typescript-linux-loong64@7.0.2': + optional: true - '@typescript-eslint/utils@8.59.3(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0(supports-color@7.2.0)) - '@typescript-eslint/scope-manager': 8.59.3 - '@typescript-eslint/types': 8.59.3 - '@typescript-eslint/typescript-estree': 8.59.3(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.3.0(supports-color@7.2.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true - '@typescript-eslint/visitor-keys@8.59.3': - dependencies: - '@typescript-eslint/types': 8.59.3 - eslint-visitor-keys: 5.0.1 + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260515.1': + '@typescript/typescript-linux-riscv64@7.0.2': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260515.1': + '@typescript/typescript-linux-s390x@7.0.2': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260515.1': + '@typescript/typescript-linux-x64@7.0.2': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260515.1': + '@typescript/typescript-netbsd-arm64@7.0.2': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260515.1': + '@typescript/typescript-netbsd-x64@7.0.2': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260515.1': + '@typescript/typescript-openbsd-arm64@7.0.2': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260515.1': + '@typescript/typescript-openbsd-x64@7.0.2': optional: true - '@typescript/native-preview@7.0.0-dev.20260515.1': - optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260515.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260515.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260515.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260515.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260515.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260515.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260515.1 + '@typescript/typescript-sunos-x64@7.0.2': + optional: true - abstract-logging@2.0.1: {} + '@typescript/typescript-win32-arm64@7.0.2': + optional: true - acorn-jsx@5.3.2(acorn@8.16.0): - dependencies: - acorn: 8.16.0 + '@typescript/typescript-win32-x64@7.0.2': + optional: true - acorn@8.16.0: {} + abstract-logging@2.0.1: {} ajv-formats@3.0.1(ajv@8.20.0): optionalDependencies: ajv: 8.20.0 - ajv@6.15.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 @@ -2254,20 +2112,12 @@ snapshots: cookie@1.1.1: {} - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - debug@4.4.3(supports-color@7.2.0): dependencies: ms: 2.1.3 optionalDependencies: supports-color: 7.2.0 - deep-is@0.1.4: {} - denque@2.1.0: {} depd@2.0.0: {} @@ -2316,82 +2166,10 @@ snapshots: escape-html@1.0.3: {} - escape-string-regexp@4.0.0: {} - - eslint-config-prettier@10.1.8(eslint@10.3.0(supports-color@7.2.0)): - dependencies: - eslint: 10.3.0(supports-color@7.2.0) - - eslint-scope@9.1.2: - dependencies: - '@types/esrecurse': 4.3.1 - '@types/estree': 1.0.9 - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint-visitor-keys@5.0.1: {} - - eslint@10.3.0(supports-color@7.2.0): - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0(supports-color@7.2.0)) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.5(supports-color@7.2.0) - '@eslint/config-helpers': 0.5.5 - '@eslint/core': 1.2.1 - '@eslint/plugin-kit': 0.7.1 - '@humanfs/node': 0.16.8 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.9 - ajv: 6.15.0 - cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@7.2.0) - escape-string-regexp: 4.0.0 - eslint-scope: 9.1.2 - eslint-visitor-keys: 5.0.1 - espree: 11.2.0 - esquery: 1.7.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - minimatch: 10.2.5 - natural-compare: 1.4.0 - optionator: 0.9.4 - transitivePeerDependencies: - - supports-color - - espree@11.2.0: - dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 5.0.1 - - esquery@1.7.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - - esutils@2.0.3: {} - fast-decode-uri-component@1.0.1: {} fast-deep-equal@3.1.3: {} - fast-json-stable-stringify@2.1.0: {} - fast-json-stringify@6.4.0: dependencies: '@fastify/merge-json-schemas': 0.2.1 @@ -2410,8 +2188,6 @@ snapshots: json-schema-ref-resolver: 3.0.0 rfdc: 1.4.1 - fast-levenshtein@2.0.6: {} - fast-querystring@1.1.2: dependencies: fast-decode-uri-component: 1.0.1 @@ -2474,32 +2250,12 @@ snapshots: dependencies: reusify: 1.1.0 - fdir@6.5.0(picomatch@4.0.4): - optionalDependencies: - picomatch: 4.0.4 - - file-entry-cache@8.0.0: - dependencies: - flat-cache: 4.0.1 - find-my-way@9.9.0: dependencies: fast-deep-equal: 3.1.3 fast-querystring: 1.1.2 safe-regex2: 5.1.1 - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@4.0.1: - dependencies: - flatted: 3.4.2 - keyv: 4.5.4 - - flatted@3.4.2: {} - fsevents@2.3.3: optional: true @@ -2511,10 +2267,6 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - glob@13.0.6: dependencies: minimatch: 10.2.5 @@ -2540,30 +2292,14 @@ snapshots: dependencies: safer-buffer: 2.1.2 - ignore@5.3.2: {} - - ignore@7.0.5: {} - - imurmurhash@0.1.4: {} - inherits@2.0.4: {} ipaddr.js@2.5.0: {} - is-extglob@2.1.1: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - is-property@1.0.2: {} - isexe@2.0.0: {} - jose@6.2.3: {} - json-buffer@3.0.1: {} - json-schema-ref-resolver@3.0.0: dependencies: dequal: 2.0.3 @@ -2576,31 +2312,14 @@ snapshots: transitivePeerDependencies: - supports-color - json-schema-traverse@0.4.1: {} - json-schema-traverse@1.0.0: {} - json-stable-stringify-without-jsonify@1.0.1: {} - - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - light-my-request@6.6.0: dependencies: cookie: 1.1.1 process-warning: 4.0.1 set-cookie-parser: 2.7.2 - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - long@5.3.2: {} lru-cache@11.5.0: {} @@ -2633,9 +2352,7 @@ snapshots: dependencies: lru.min: 1.1.5 - natural-compare@1.4.0: {} - - node@runtime:24.16.0: {} + node@runtime:24.21.0: {} nodemailer@10.0.15: {} @@ -2643,36 +2360,45 @@ snapshots: openapi-types@12.1.3: {} - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - path-exists@4.0.0: {} + oxlint-tsgolint@7.0.2003: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 7.0.2003 + '@oxlint-tsgolint/darwin-x64': 7.0.2003 + '@oxlint-tsgolint/linux-arm64': 7.0.2003 + '@oxlint-tsgolint/linux-x64': 7.0.2003 + '@oxlint-tsgolint/win32-arm64': 7.0.2003 + '@oxlint-tsgolint/win32-x64': 7.0.2003 + + oxlint@1.86.0(oxlint-tsgolint@7.0.2003): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.86.0 + '@oxlint/binding-android-arm64': 1.86.0 + '@oxlint/binding-darwin-arm64': 1.86.0 + '@oxlint/binding-darwin-x64': 1.86.0 + '@oxlint/binding-freebsd-x64': 1.86.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.86.0 + '@oxlint/binding-linux-arm-musleabihf': 1.86.0 + '@oxlint/binding-linux-arm64-gnu': 1.86.0 + '@oxlint/binding-linux-arm64-musl': 1.86.0 + '@oxlint/binding-linux-ppc64-gnu': 1.86.0 + '@oxlint/binding-linux-riscv64-gnu': 1.86.0 + '@oxlint/binding-linux-riscv64-musl': 1.86.0 + '@oxlint/binding-linux-s390x-gnu': 1.86.0 + '@oxlint/binding-linux-x64-gnu': 1.86.0 + '@oxlint/binding-linux-x64-musl': 1.86.0 + '@oxlint/binding-openharmony-arm64': 1.86.0 + '@oxlint/binding-win32-arm64-msvc': 1.86.0 + '@oxlint/binding-win32-ia32-msvc': 1.86.0 + '@oxlint/binding-win32-x64-msvc': 1.86.0 + oxlint-tsgolint: 7.0.2003 path-expression-matcher@1.5.0: {} - path-key@3.1.1: {} - path-scurry@2.0.2: dependencies: lru-cache: 11.5.0 minipass: 7.1.3 - picomatch@4.0.4: {} - pino-abstract-transport@3.0.0: dependencies: split2: 4.2.0 @@ -2695,16 +2421,12 @@ snapshots: postal-mime@2.7.4: {} - prelude-ls@1.2.1: {} - prettier@3.8.3: {} process-warning@4.0.1: {} process-warning@5.1.0: {} - punycode@2.3.1: {} - quick-format-unescaped@4.0.4: {} real-require@0.2.0: {} @@ -2736,20 +2458,12 @@ snapshots: secure-json-parse@4.1.0: {} - semver@7.8.0: {} - semver@7.8.5: {} set-cookie-parser@2.7.2: {} setprototypeof@1.2.0: {} - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - sonic-boom@4.2.1: dependencies: atomic-sleep: 1.0.0 @@ -2783,21 +2497,12 @@ snapshots: dependencies: real-require: 1.0.0 - tinyglobby@0.2.16: - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - toad-cache@3.7.1: {} toad-cache@3.7.4: {} toidentifier@1.0.1: {} - ts-api-utils@2.5.0(typescript@6.0.3): - dependencies: - typescript: 6.0.3 - tslib@2.8.1: {} tsx@4.22.0: @@ -2806,43 +2511,37 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - - typescript-eslint@8.59.3(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.59.3(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.59.3(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.3(eslint@10.3.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.3.0(supports-color@7.2.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - typescript@6.0.3: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 undici-types@7.18.2: {} unpdf@1.6.2: {} - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - word-wrap@1.2.5: {} - xml-naming@0.1.0: {} yaml@2.9.0: {} - yocto-queue@0.1.0: {} - zod-openapi@5.4.6(zod@4.4.3): dependencies: zod: 4.4.3 diff --git a/scripts/README.md b/scripts/README.md index 6215716eb..ce038240d 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,7 +1,8 @@ # scripts/ -Automação de **desenvolvimento, CI e operações** do core-api — TypeScript (Node 24 + -`--experimental-strip-types`) e bash. **Não** é código de produção (esse vive em `src/`). +Automação de **desenvolvimento, CI e operações** do core-api — TypeScript executado direto pelo +Node 24 (`node arquivo.ts`; o type stripping é default, sem flag) e bash. **Não** é código de +produção (esse vive em `src/`). > Programas auxiliares **compilados** (ex.: o dead-man's switch em Go) ficam em > [`../tools/`](../tools/README.md), não aqui. A distinção: `scripts/` = automação **interpretada** diff --git a/scripts/ci/test-integration.ts b/scripts/ci/test-integration.ts index 805687c8d..947677032 100644 --- a/scripts/ci/test-integration.ts +++ b/scripts/ci/test-integration.ts @@ -316,12 +316,14 @@ const dockerDown = (): void => { }; const runNodeTest = (suite: Suite): number => { + // `--experimental-strip-types` e `--no-warnings` saíram (redundantes no Node 24, ver + // `tests/cleanup/node-flags-not-redundant.test.ts`). `--enable-source-maps` FICA: o + // `drizzle-orm` publica source map, e estas suítes rodam contra MySQL real — é exatamente onde + // um stack trace apontando o `.ts` da lib em vez do `.js` empacotado paga por si. const flags = [ + '--enable-source-maps', '--test', ...(suite.concurrency1 ? ['--test-concurrency=1'] : []), - '--experimental-strip-types', - '--enable-source-maps', - '--no-warnings', ]; return ( spawnSync('node', [...flags, ...suite.paths], { diff --git a/scripts/claude/logbook.ts b/scripts/claude/logbook.ts index 0567d9d9c..02842c0d3 100644 --- a/scripts/claude/logbook.ts +++ b/scripts/claude/logbook.ts @@ -12,9 +12,9 @@ * incidente aberto — e não um problema desta máquina. * * Uso: - * node --experimental-strip-types scripts/claude/logbook.ts # todas as sessões - * node --experimental-strip-types scripts/claude/logbook.ts --dead # só as que não fecharam - * node --experimental-strip-types scripts/claude/logbook.ts # outro diário + * node scripts/claude/logbook.ts # todas as sessões + * node scripts/claude/logbook.ts --dead # só as que não fecharam + * node scripts/claude/logbook.ts # outro diário */ import { readFileSync } from 'node:fs'; diff --git a/scripts/data/fetch-ibge-municipios.ts b/scripts/data/fetch-ibge-municipios.ts index bfbd4ee67..56a364e1c 100644 --- a/scripts/data/fetch-ibge-municipios.ts +++ b/scripts/data/fetch-ibge-municipios.ts @@ -1,7 +1,7 @@ // Baixa o catálogo oficial de municípios do IBGE (view nivelado) e salva em // .tmp/ibge-municipios.json. Roda no terminal do dev (precisa de rede). // -// node --experimental-strip-types scripts/data/fetch-ibge-municipios.ts +// node scripts/data/fetch-ibge-municipios.ts // // É tooling de apoio ao ticket PARTNERS-MUNICIPALITY-LOOKUP — a transformação // JSON → municipalities.data.ts é feita pelo módulo de domínio, não aqui. diff --git a/scripts/data/generate-municipalities.ts b/scripts/data/generate-municipalities.ts index dfcf07e5c..e8bedbf2b 100644 --- a/scripts/data/generate-municipalities.ts +++ b/scripts/data/generate-municipalities.ts @@ -1,7 +1,7 @@ // Gera src/modules/partners/domain/geography/municipalities.data.ts a partir de // .tmp/ibge-municipios.json (baixado por fetch-ibge-municipios.ts). // -// node --experimental-strip-types scripts/data/generate-municipalities.ts +// node scripts/data/generate-municipalities.ts // // Build-time only: o transform IBGE→domínio acontece aqui; o runtime carrega o // array já normalizado. O data file gerado É commitado (faz parte do source/seed). @@ -37,7 +37,7 @@ const main = async (): Promise => { const body = `// GERADO — não editar à mão. // Fonte: IBGE localidades/municipios (view=nivelado). Total: ${String(tuples.length)}. -// Regenerar: node --experimental-strip-types scripts/data/generate-municipalities.ts +// Regenerar: node scripts/data/generate-municipalities.ts // Tupla compacta [cod IBGE (7 dígitos), nome, sigla UF]. Normalização para o VO // Municipality acontece em municipality.ts (runtime). diff --git a/scripts/e2e/auth.sh b/scripts/e2e/auth.sh index c3684559e..88552a184 100644 --- a/scripts/e2e/auth.sh +++ b/scripts/e2e/auth.sh @@ -28,7 +28,7 @@ e2e_compose up -d mysql --wait || exit 1 # Provisiona o schema (CORE-MIGRATE-BOOT-INVERT: o server NÃO migra mais no boot). MIGRATE_DATABASE_URL='mysql://root:rootpw-migration-test-only@127.0.0.1:3306/core' \ - node --experimental-strip-types --enable-source-maps --no-warnings src/jobs/migrate/run.ts || exit 1 + node --enable-source-maps src/jobs/migrate/run.ts || exit 1 # Servidor real em background (applyMigrations:false — schema já provisionado acima). # @@ -40,11 +40,11 @@ DB='mysql://root:rootpw-migration-test-only@127.0.0.1:3306/core' \ PORT=3100 \ LOG_LEVEL=warn \ - node --experimental-strip-types --enable-source-maps --no-warnings src/server.ts & + node --enable-source-maps src/server.ts & SRV=$! # disown: o shell não anuncia "Terminated" quando o trap mata o server no teardown. disown "$SRV" 2>/dev/null || true # Smoke via fetch — espera o /health no before(); seu exit code vira o do script (trap preserva). E2E_BASE_URL=http://127.0.0.1:3100 \ - node --test --experimental-strip-types --enable-source-maps --no-warnings tests/e2e/auth-smoke.e2e.ts + node --enable-source-maps --test tests/e2e/auth-smoke.e2e.ts diff --git a/scripts/e2e/collaborators.sh b/scripts/e2e/collaborators.sh index 37b387147..3426af437 100755 --- a/scripts/e2e/collaborators.sh +++ b/scripts/e2e/collaborators.sh @@ -46,7 +46,7 @@ e2e_compose up -d mysql --wait || exit 1 # Provisiona o schema (CORE-MIGRATE-BOOT-INVERT: o server NÃO migra mais no boot). MIGRATE_DATABASE_URL="mysql://root:rootpw-migration-test-only@127.0.0.1:${MYSQL_PORT}/core" \ - node --experimental-strip-types --enable-source-maps --no-warnings src/jobs/migrate/run.ts || exit 1 + node --enable-source-maps src/jobs/migrate/run.ts || exit 1 # Servidor real em background (applyMigrations:false — schema já provisionado). O operador RBAC é # semeado por CORE_API_E2E=1 + AUTH_SEED_JSON (collaborator:read+write). @@ -63,10 +63,10 @@ CORE_API_E2E=1 \ AUTH_SEED_JSON='{"users":[{"email":"e2e-rh@example.com","password":"Str0ng-Passphrase-2026!","permissions":["collaborator:read","collaborator:write"]}]}' \ PORT=3100 \ LOG_LEVEL=warn \ - node --experimental-strip-types --enable-source-maps --no-warnings src/server.ts & + node --enable-source-maps src/server.ts & SRV=$! disown "$SRV" 2>/dev/null || true # Smoke via fetch — espera o /health no before(); seu exit code vira o do script (trap preserva). E2E_BASE_URL=http://127.0.0.1:3100 \ - node --test --experimental-strip-types --enable-source-maps --no-warnings tests/e2e/collaborators-smoke.e2e.ts + node --enable-source-maps --test tests/e2e/collaborators-smoke.e2e.ts diff --git a/scripts/e2e/contracts.sh b/scripts/e2e/contracts.sh index 3dfe55f80..ecb977d64 100644 --- a/scripts/e2e/contracts.sh +++ b/scripts/e2e/contracts.sh @@ -39,7 +39,7 @@ e2e_compose up -d mysql --wait || exit 1 # Provisiona o schema (CORE-MIGRATE-BOOT-INVERT: o server NÃO migra mais no boot). MIGRATE_DATABASE_URL='mysql://root:rootpw-migration-test-only@127.0.0.1:3306/core' \ - node --experimental-strip-types --enable-source-maps --no-warnings src/jobs/migrate/run.ts || exit 1 + node --enable-source-maps src/jobs/migrate/run.ts || exit 1 # Servidor real em background (applyMigrations:false — schema já provisionado). Semeia o operador # RBAC (CORE_API_E2E=1 + AUTH_SEED_JSON). Dual-pool: writer=root, reader=readonly_bi (SELECT-only). @@ -60,10 +60,10 @@ S3_REGION=us-east-1 \ AUTH_SEED_JSON='{"users":[{"email":"e2e-operator@example.com","password":"Str0ng-Passphrase-2026!","permissions":["contract:read","contract:write"]}]}' \ PORT=3100 \ LOG_LEVEL=warn \ - node --experimental-strip-types --enable-source-maps --no-warnings src/server.ts & + node --enable-source-maps src/server.ts & SRV=$! disown "$SRV" 2>/dev/null || true # Smoke via fetch — espera o /health no before(); seu exit code vira o do script (trap preserva). E2E_BASE_URL=http://127.0.0.1:3100 \ - node --test --experimental-strip-types --enable-source-maps --no-warnings tests/e2e/contracts-smoke.e2e.ts + node --enable-source-maps --test tests/e2e/contracts-smoke.e2e.ts diff --git a/scripts/etl/orchestrate.ts b/scripts/etl/orchestrate.ts index 6b8f3ecb1..d869b9fa6 100644 --- a/scripts/etl/orchestrate.ts +++ b/scripts/etl/orchestrate.ts @@ -123,10 +123,10 @@ const quarantine = async ( // Drena as `failures` de decode (reader) de uma entidade para a quarentena, contando // cada uma como `read` + `quarantined` (D12 — fonte 1 de 3). -const drainDecodeFailures = async ( +const drainDecodeFailures = async ( sink: QuarantineSink, table: EntityName, - read: TableRead, + read: TableRead, tally: EntityTally, ): Promise => { let next = tally; diff --git a/scripts/setup/secrets.ts b/scripts/setup/secrets.ts index 8eab98ffb..fdb11d0cf 100644 --- a/scripts/setup/secrets.ts +++ b/scripts/setup/secrets.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env -S node --experimental-strip-types --no-warnings +#!/usr/bin/env node // scripts/setup/secrets.ts // // Gera os 3 arquivos de secret consumidos pelo compose MySQL. Veja design @@ -156,7 +156,7 @@ const promptSilent = async (label: string): Promise => let buffer = ''; - // Function declarations (não arrow) — eslint permite hoisting (`functions: false`) + // Function declarations (não arrow) — o lint permite hoisting (`functions: false`) // e cleanup precisa referenciar onData (e vice-versa via remoção do listener). function cleanup(): void { stdin.removeListener('data', onData); diff --git a/src/modules/auth/adapters/http/schemas.ts b/src/modules/auth/adapters/http/schemas.ts index 7b01d2dae..ed760835b 100644 --- a/src/modules/auth/adapters/http/schemas.ts +++ b/src/modules/auth/adapters/http/schemas.ts @@ -6,7 +6,7 @@ import * as z from 'zod/v4'; export const registerBodySchema = z.object({ - email: z.string().meta({ description: 'E-mail do usuário', example: 'user@example.com' }), + email: z.string().meta({ description: 'E-mail do usuário', examples: ['user@example.com'] }), password: z.string().meta({ description: 'Senha em claro (validada pela policy do domínio)' }), }); @@ -63,6 +63,12 @@ export const resetPasswordBodySchema = z.object({ // USR-PASSWORD-POLICY: política consumível pelo front (fonte única dos comprimentos). Expõe APENAS os // limites — NUNCA a blocklist (revelá-la permitiria contorná-la). Endpoint público, sem autenticação. export const passwordPolicyResponseSchema = z.object({ - minLength: z.number().int().meta({ description: 'Comprimento mínimo da senha', example: 12 }), - maxLength: z.number().int().meta({ description: 'Comprimento máximo da senha', example: 128 }), + minLength: z + .number() + .int() + .meta({ description: 'Comprimento mínimo da senha', examples: [12] }), + maxLength: z + .number() + .int() + .meta({ description: 'Comprimento máximo da senha', examples: [128] }), }); diff --git a/src/modules/budget-plans/adapters/http/schemas.ts b/src/modules/budget-plans/adapters/http/schemas.ts index 5febb0506..b764b8ab9 100644 --- a/src/modules/budget-plans/adapters/http/schemas.ts +++ b/src/modules/budget-plans/adapters/http/schemas.ts @@ -161,7 +161,7 @@ export const lifecyclePlanResponseSchema = z.object({ year: z.number().int(), programRef: uuidV4(), status: budgetPlanStatusSchema, - version: z.string().meta({ description: 'Versão do plano (major.minor)', example: '2.0' }), + version: z.string().meta({ description: 'Versão do plano (major.minor)', examples: ['2.0'] }), scenarioName: z.string().nullable(), parentId: uuidV4().nullable(), totalInCents: z.number().int(), diff --git a/src/modules/contracts/adapters/persistence/repos/outbox-repository.drizzle.ts b/src/modules/contracts/adapters/persistence/repos/outbox-repository.drizzle.ts index 796fe7839..c2fa404ce 100644 --- a/src/modules/contracts/adapters/persistence/repos/outbox-repository.drizzle.ts +++ b/src/modules/contracts/adapters/persistence/repos/outbox-repository.drizzle.ts @@ -420,7 +420,7 @@ export const createDrizzleOutboxRepository = ( // ── moveToDeadLetter ────────────────────────────────────────────────────── // Implementação direta (sem safe()) para distinguir OutboxEventNotFound de // erros genéricos de I/O. Usa um Result retornado - // pela tx interna como canal de controle — sem `class` (ESLint proíbe). + // pela tx interna como canal de controle — sem `class` (o gate `lint-gaps` proíbe). const moveToDeadLetterFinal = async ( consumerId: string, diff --git a/src/modules/contracts/application/ports/document-storage.ts b/src/modules/contracts/application/ports/document-storage.ts index bd78a5383..776719097 100644 --- a/src/modules/contracts/application/ports/document-storage.ts +++ b/src/modules/contracts/application/ports/document-storage.ts @@ -47,7 +47,7 @@ export type DocumentStorage = Readonly<{ // `Uint8Array` não tem variant `readonly` nativo no TypeScript 6, e // `Readonly` não impede `.set(0, x)`. A regra // `prefer-readonly-parameter-types` fica desabilitada apenas aqui — o - // contrato é "adapter consome `bytes` sem mutar" e ESLint não consegue + // contrato é "adapter consome `bytes` sem mutar" e o lint não consegue // expressar essa garantia para tipos não-readonly nativos. // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types upload: (input: UploadInput) => Promise>; diff --git a/src/modules/financial/adapters/persistence/repos/expected-counterpart-store.in-memory.ts b/src/modules/financial/adapters/persistence/repos/expected-counterpart-store.in-memory.ts index b7a7331be..135c24157 100644 --- a/src/modules/financial/adapters/persistence/repos/expected-counterpart-store.in-memory.ts +++ b/src/modules/financial/adapters/persistence/repos/expected-counterpart-store.in-memory.ts @@ -18,7 +18,7 @@ import type { // `store` é injetável (Map) para COMPARTILHAR com o reconciliation-repo in-memory — a US2 (`match`) muta // a contrapartida na mesma unit-of-work que a perna de B (paridade da tx atômica do Drizzle). export const createInMemoryExpectedCounterpartStore = ( - store: Map = new Map(), + store = new Map(), outbox: FinancialOutbox = createInMemoryOutbox().port, ): ExpectedCounterpartStore => { return { diff --git a/src/modules/financial/adapters/persistence/repos/remittance-repository.drizzle.ts b/src/modules/financial/adapters/persistence/repos/remittance-repository.drizzle.ts index 0037e0549..2315cb119 100644 --- a/src/modules/financial/adapters/persistence/repos/remittance-repository.drizzle.ts +++ b/src/modules/financial/adapters/persistence/repos/remittance-repository.drizzle.ts @@ -34,8 +34,8 @@ const logRepo = (op: string, cause: unknown): void => { // ESPERADO sob concorrência — de "o banco caiu", e faz isso por identidade (`===`), não por // mensagem, que refatoração muda sem avisar. // -// Instância única e `Error` de verdade, por duas restrições que se somam: `@typescript-eslint/ -// only-throw-error` recusa lançar símbolo, e `class` é barrada neste repositório, então `instanceof` +// Instância única e `Error` de verdade, por duas restrições que se somam: o lint +// (`only-throw-error`) recusa lançar símbolo, e `class` é barrada neste repositório, então `instanceof` // de um tipo próprio está fora. Uma constante compartilhada resolve as duas. // // ⚠️ O `stack` dela aponta para a carga deste módulo, não para o ponto do `throw` — o preço de ser diff --git a/src/modules/financial/application/ports/document-reader.ts b/src/modules/financial/application/ports/document-reader.ts index 48905aeb0..4ebfe10ad 100644 --- a/src/modules/financial/application/ports/document-reader.ts +++ b/src/modules/financial/application/ports/document-reader.ts @@ -12,7 +12,7 @@ export type DocumentReaderInput = Readonly<{ export type DocumentReaderPort = Readonly<{ // `Uint8Array` (em `bytes`) não tem variant `readonly` nativo no TS 6, e `Readonly` // não impede `.set(...)`. Segue o precedente de `contracts/.../document-storage.ts`: o contrato é - // "reader consome `bytes` sem mutar"; o ESLint não expressa isso para tipos não-readonly nativos. + // "reader consome `bytes` sem mutar"; o lint não expressa isso para tipos não-readonly nativos. // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types read: (input: DocumentReaderInput) => Promise>; }>; diff --git a/src/modules/financial/application/use-cases/undo-reconciliation.ts b/src/modules/financial/application/use-cases/undo-reconciliation.ts index 85ffb4a0a..409f91a21 100644 --- a/src/modules/financial/application/use-cases/undo-reconciliation.ts +++ b/src/modules/financial/application/use-cases/undo-reconciliation.ts @@ -107,7 +107,7 @@ export const undoReconciliation = if (!cpR.ok) return err(cpR.error); const counterpart = cpR.value; - if (counterpart !== null && counterpart.status === 'Pending') { + if (counterpart?.status === 'Pending') { // Nunca casada → descarta (nada órfão em B). const discarded = discard(counterpart); if (!discarded.ok) return err(discarded.error); @@ -118,7 +118,7 @@ export const undoReconciliation = [...undone.value.events, ...discarded.value.events], ); if (!saved.ok) return err(saved.error); - } else if (counterpart !== null && counterpart.status === 'Matched') { + } else if (counterpart?.status === 'Matched') { // Já casada → reabre a contrapartida e desfaz a perna B (a transação real volta a Pending). const reopened = reopen(counterpart); if (!reopened.ok) return err(reopened.error); @@ -151,7 +151,7 @@ export const undoReconciliation = if (!cpDestR.ok) return err(cpDestR.error); const cpDest = cpDestR.value; - if (cpDest !== null && cpDest.status === 'Matched') { + if (cpDest?.status === 'Matched') { // Guard de simetria: só REABRE a expectativa (Matched → Pending) na conta de destino. NÃO // cascateia outra conciliação (a própria B já é o `undone` principal) e NÃO toca a origem/perna A. const reopened = reopen(cpDest); diff --git a/src/modules/financial/domain/payout/account-check-digit.ts b/src/modules/financial/domain/payout/account-check-digit.ts index bff176e91..33a31e83e 100644 --- a/src/modules/financial/domain/payout/account-check-digit.ts +++ b/src/modules/financial/domain/payout/account-check-digit.ts @@ -58,7 +58,7 @@ export type CheckDigitUnverifiable = 'unsupported-bank' | 'account-not-numeric'; // depois do padding. Há teste fixando essa equivalência. export const bradescoAccountCheckDigits = (accountDigits: string): readonly string[] => { // Percorre da esquerda para a direita e deriva a posição a partir da DIREITA — o `for…of` sobre a - // string evita tanto o spread (que o ESLint barra por decompor caracteres compostos) quanto o + // string evita tanto o spread (que o lint barra por decompor caracteres compostos) quanto o // acesso indexado (que `noUncheckedIndexedAccess` obrigaria a destratar com `?? '0'`, escondendo // um índice fora de faixa atrás de um zero plausível). Mesmo idioma de `auth/domain/identity/cpf.ts`. let weightedSum = 0; diff --git a/src/modules/partners/adapters/persistence/repos/contract-count-store.in-memory.ts b/src/modules/partners/adapters/persistence/repos/contract-count-store.in-memory.ts index 76a09d15f..1d2a1a9f3 100644 --- a/src/modules/partners/adapters/persistence/repos/contract-count-store.in-memory.ts +++ b/src/modules/partners/adapters/persistence/repos/contract-count-store.in-memory.ts @@ -9,7 +9,7 @@ import { ok } from '#src/shared/primitives/result.ts'; import type { ContractCountStore } from '#src/modules/partners/application/ports/contract-count-store.ts'; export const makeInMemoryContractCountStore = ( - seed: readonly { contractorRef: string; activeCount: number }[] = [], + seed: readonly Readonly<{ contractorRef: string; activeCount: number }>[] = [], ): ContractCountStore => { const counts = new Map(seed.map((s) => [s.contractorRef, s.activeCount])); const applied = new Set(); diff --git a/src/modules/partners/domain/geography/municipalities.data.ts b/src/modules/partners/domain/geography/municipalities.data.ts index 9b2b1f496..6405f1466 100644 --- a/src/modules/partners/domain/geography/municipalities.data.ts +++ b/src/modules/partners/domain/geography/municipalities.data.ts @@ -1,6 +1,6 @@ // GERADO — não editar à mão. // Fonte: IBGE localidades/municipios (view=nivelado). Total: 5571. -// Regenerar: node --experimental-strip-types scripts/data/generate-municipalities.ts +// Regenerar: node scripts/data/generate-municipalities.ts // Tupla compacta [cod IBGE (7 dígitos), nome, sigla UF]. Normalização para o VO // Municipality acontece em municipality.ts (runtime). diff --git a/src/server.ts b/src/server.ts index d6c6e4a9a..6bbad5940 100644 --- a/src/server.ts +++ b/src/server.ts @@ -168,7 +168,7 @@ const main = async (): Promise => { // O banner sai INCONDICIONALMENTE, e é a forma honesta enquanto a linha acima existir: envolvê-lo // num `if (rbacMode === 'bypass')` seria escrever uma condição que o compilador prova sempre - // verdadeira — o ESLint recusa, e com razão. Ao religar, o `if` volta junto com o modo dinâmico. + // verdadeira — o lint recusa, e com razão. Ao religar, o `if` volta junto com o modo dinâmico. process.stderr.write(rbacBypassBanner(process.env['NODE_ENV'] ?? 'undefined')); // A divergência entre o CONFIGURADO e o EFETIVO é o que o operador não consegue deduzir do banner: @@ -181,7 +181,7 @@ const main = async (): Promise => { ); } // Os `driver: 'mysql'` daqui para baixo são literais, e não mais derivados de um ternário sobre a - // env: sob o ADR-0068 a guarda só devolve `mysql`, e o ESLint (`no-unnecessary-condition`) recusa + // env: sob o ADR-0068 a guarda só devolve `mysql`, e o lint (`no-unnecessary-condition`) recusa // a comparação que sobrou. A variante `'memory'` continua existindo em cada // `CompositionConfig` — mas só alcançável por injeção em teste, nunca por configuração. const authDeps = await buildAuthHttpDeps({ @@ -255,7 +255,7 @@ const main = async (): Promise => { // `financial`. Afrouxá-la gravaria linha que sobrevive ao religar, e o #634 não desfaz banco. // // Literal `true`, e não `rbacMode === 'bypass'`, pela mesma razão do banner acima: com o modo - // fixado por código, o compilador prova a comparação sempre verdadeira e o ESLint a recusa. + // fixado por código, o compilador prova a comparação sempre verdadeira e o lint a recusa. // // ⚠️ ← religar: este literal é a SEGUNDA das duas linhas marcadas `← religar`. Apagar só a // outra deixa o afrouxamento do domínio de pé com o RBAC já enforçado — e nada mecânico acusa: diff --git a/src/shared/kernel/non-zero-money.ts b/src/shared/kernel/non-zero-money.ts index 8c653fa9c..9554fb62e 100644 --- a/src/shared/kernel/non-zero-money.ts +++ b/src/shared/kernel/non-zero-money.ts @@ -34,8 +34,10 @@ import type { Money } from './money.ts'; * * Referência: `handbook/interviews/0001-functional-ddd-domain-refresh.md`, * Bloco D D5 (Invariantes contextuais) — DO D§25. + * + * O duplo underscore de `__nonZeroMoney` é exceção declarada do `naming-convention`, na allowlist + * de `tests/cleanup/lint-gaps.test.ts`. */ -// eslint-disable-next-line @typescript-eslint/naming-convention export type NonZeroMoney = Money & { readonly __nonZeroMoney: true }; export type NonZeroMoneyError = 'money-must-be-non-zero'; diff --git a/src/shared/primitives/brand.ts b/src/shared/primitives/brand.ts index cfc7d9eae..56d2959cc 100644 --- a/src/shared/primitives/brand.ts +++ b/src/shared/primitives/brand.ts @@ -10,10 +10,9 @@ * NUNCA em agregados — vide Bloco A DON'T §1 do master doc. */ // Nome canônico `__brand` é prescrito literalmente pela entrevista 0001 §6 (followup -// L261-294) como pattern idiomático TS para brand symbols. O `naming-convention` da -// flat config aceita 1 leading underscore (`leadingUnderscore: 'allow'`), não 2 — -// disable cirúrgico aqui é mais conservador que ampliar a regra globalmente. -// eslint-disable-next-line @typescript-eslint/naming-convention +// L261-294) como pattern idiomático TS para brand symbols. O `naming-convention` aceita +// 1 leading underscore (`leadingUnderscore: 'allow'`), não 2 — a exceção cirúrgica vive na +// allowlist de `tests/cleanup/lint-gaps.test.ts`, mais conservadora que ampliar a regra. declare const __brand: unique symbol; /** diff --git a/src/shared/runtime/env-echo.ts b/src/shared/runtime/env-echo.ts index 2ffec7835..555f35b81 100644 --- a/src/shared/runtime/env-echo.ts +++ b/src/shared/runtime/env-echo.ts @@ -48,7 +48,7 @@ const REPLACEMENT = '?'; * - `slice` corta por unidade UTF-16 e parte um emoji fora do BMP ao meio, deixando um par * substituto solto; * - o spread (`[...value]`) corta por code point e nao parte pares, mas **decompoe** sequencia com - * ZWJ — uma familia vira quatro emojis soltos no meio do diagnostico. E o que o ESLint + * ZWJ — uma familia vira quatro emojis soltos no meio do diagnostico. E o que o lint * (`no-misused-spread`) recusa, e ele esta certo; * - `Intl.Segmenter` corta onde a pessoa que le enxerga uma letra. * diff --git a/tests/cleanup/application-depends-inward.test.ts b/tests/cleanup/application-depends-inward.test.ts index b7575a209..c4607e9c8 100644 --- a/tests/cleanup/application-depends-inward.test.ts +++ b/tests/cleanup/application-depends-inward.test.ts @@ -16,8 +16,8 @@ * devolve vazio. Duas rules (`application.md`, `contracts-module.md`) se apoiavam nela. A adesão * medida é de 247 arquivos e zero violações, sustentada só por disciplina. * - * A segunda asserção cobre `interface` em port. `class` já é barrado globalmente por ESLint - * (`no-restricted-syntax`), mas `interface` não é barrado por nada — e um port declarado como + * A segunda asserção cobre `interface` em port. `class` já é barrado globalmente pelo gate de AST + * (`tests/cleanup/lint-gaps.test.ts`), mas `interface` não é barrado por nada — e um port declarado como * `interface` aceita `implements`, o que reabre a porta para hierarquia de classes que o projeto * fechou ao escolher `type` + funções. */ diff --git a/tests/cleanup/domain-no-throw.test.ts b/tests/cleanup/domain-no-throw.test.ts index 917525d54..ba5f13ab2 100644 --- a/tests/cleanup/domain-no-throw.test.ts +++ b/tests/cleanup/domain-no-throw.test.ts @@ -11,8 +11,9 @@ * O custo é assimétrico e por isso a regra é absoluta aqui. Esquecer de tratar um `Result` é erro * de compilação; esquecer de capturar uma exceção é incidente em produção. * - * Este gate cobre a lacuna real: `class`, `any` e `switch` não-exaustivo JÁ são barrados por ESLint - * (`no-restricted-syntax`, `no-explicit-any`, `switch-exhaustiveness-check`) e pelo `tsconfig` + * Este gate cobre a lacuna real: `class`, `any` e `switch` não-exaustivo JÁ são barrados pelo gate + * de AST `lint-gaps.test.ts` (`class`), pelo oxlint (`no-explicit-any`, + * `switch-exhaustiveness-check`) e pelo `tsconfig` * (`noFallthroughCasesInSwitch`) — mas NADA barrava `throw`. A norma se sustentava por disciplina, * com adesão de 100% em ~180 arquivos de domínio e kernel. * diff --git a/tests/cleanup/error-literal-casing.test.ts b/tests/cleanup/error-literal-casing.test.ts index 74ee70e2a..be7219661 100644 --- a/tests/cleanup/error-literal-casing.test.ts +++ b/tests/cleanup/error-literal-casing.test.ts @@ -6,7 +6,7 @@ * `switch` do chamador, e o que separa o erro INTERNO (que o código casa) da mensagem AO HUMANO * (PT-BR, formatada na borda). * - * O `@typescript-eslint/naming-convention` cobre o casing de IDENTIFICADOR — `const`, `type`, + * O `naming-convention` (gate de AST em `lint-gaps.test.ts`) cobre o casing de IDENTIFICADOR — `const`, `type`, * `function`. Ele não vê o VALOR de uma string literal, que é exatamente onde o erro vive. Um * `'ContractNotActive'` ou `'contrato_nao_ativo'` atravessava o repositório inteiro sem um vermelho. * diff --git a/tests/cleanup/lint-gaps.test.ts b/tests/cleanup/lint-gaps.test.ts new file mode 100644 index 000000000..54c45dd90 --- /dev/null +++ b/tests/cleanup/lint-gaps.test.ts @@ -0,0 +1,173 @@ +/** + * LINT-GAPS — o que o ESLint cobrava e o `oxlint` não tem: `class`, casing e ordem de membros. + * + * Molde: tests/cleanup/*.test.ts (varrem o fonte e exigem um estado desejado). + * + * Por que existe: o `oxlint` cobre 154 das 158 regras que o ESLint aplicava neste repositório. Das + * quatro restantes, `no-octal` é recusado pelo próprio compilador (`TS1121`); as outras três vivem + * aqui, reimplementadas sobre a AST do TypeScript 7 em `tests/support/lint-gaps.ts`: + * + * - **`no-class`** — `no-restricted-syntax` sobre `ClassDeclaration`/`ClassExpression`. Invariante + * do CLAUDE.md: tipos são `type Readonly<{}>`, comportamento é função. + * - **`naming-convention`** — o casing que o CLAUDE.md declara enforced. Fora de `tests/`, como no + * `eslint.config.js` (lá a regra é `off` para fixtures usarem `c`, `r`, `a`). + * - **`member-ordering`** — ordem padrão dos membros de `interface` e type literal. + * + * Fidelidade: na migração (05/10/2026), a reimplementação acusou exatamente os mesmos 46 nomes que + * o ESLint em 2033 arquivos (com a regra forçada também nos testes), e os fixtures do autoteste + * foram conferidos contra o ESLint linha a linha. + */ + +import { describe, it } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { execFileSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +import { findClasses, findMemberOrder, findNaming } from '../support/lint-gaps.ts'; +import type { GapFinding } from '../support/lint-gaps.ts'; +import { PROJECT_ROOT } from '../support/source-scan.ts'; +import { withProject } from '../support/ts-ast.ts'; + +/** + * Nomes fora do padrão por DECISÃO, cada um com o motivo. Chave por arquivo + nome, não por + * linha: a exceção sobrevive a edição acima dela e morre se o nome mudar. + */ +const NAMING_EXCEPTIONS: readonly Readonly<{ file: string; name: string; reason: string }>[] = [ + { + file: 'src/shared/kernel/non-zero-money.ts', + name: '__nonZeroMoney', + reason: 'marcador de branded type — o duplo underscore evita colisão com propriedade real', + }, + { + file: 'src/shared/primitives/brand.ts', + name: '__brand', + reason: 'símbolo do branded type — o duplo underscore evita colisão com propriedade real', + }, +]; + +/** Diretórios que o `eslint.config.js` ignorava e que o tsconfig ainda inclui. */ +const IGNORED_PREFIXES = ['tests/reports/']; + +type Located = GapFinding & Readonly<{ file: string }>; + +/** + * `.ts` versionados — pergunta ao git, não ao disco (rule `testing.md`): arquivo não versionado + * existe só na máquina de quem escreve, e gate cuja resposta depende de onde roda não verifica. + */ +const trackedTs = (): ReadonlySet => + new Set( + execFileSync('git', ['ls-files', 'src', 'tests', 'scripts', 'db'], { + cwd: PROJECT_ROOT, + encoding: 'utf8', + }) + .split('\n') + .filter((p) => p.endsWith('.ts') && !IGNORED_PREFIXES.some((d) => p.startsWith(d))), + ); + +/** + * `unscanned` é o que separa este gate de um verde por vacuidade: são os `.ts` **versionados** que + * o programa do TypeScript não entrega, por caírem fora dos globs de `include` do `tsconfig.json`. + * Arquivo assim é rastreado e nunca varrido — escapa do banimento de `class` e da convenção de + * nomes sem que nada acuse. + * + * A guarda anterior era `scanned > 1500`, um limiar: com 2041 arquivos hoje, dezenas poderiam + * escapar sem derrubar o número. Contagem não é a propriedade — a propriedade é **nenhum arquivo + * versionado de fora**, e é ela que se afirma aqui (rule `testing.md`). + */ +const scan = (): Readonly<{ + scanned: number; + trackedCount: number; + unscanned: readonly string[]; + findings: readonly Located[]; +}> => { + const tracked = trackedTs(); + const rel = (abs: string): string => abs.slice(PROJECT_ROOT.length + 1); + return withProject( + join(PROJECT_ROOT, 'tsconfig.json'), + (abs) => tracked.has(rel(abs)), + (files) => { + const seen = new Set(files.map((sf) => rel(sf.fileName))); + return { + scanned: files.length, + trackedCount: tracked.size, + unscanned: [...tracked].filter((p) => !seen.has(p)).sort(), + findings: files.flatMap((sf) => { + const file = rel(sf.fileName); + const naming = file.startsWith('tests/') ? [] : findNaming(sf); + return [...findClasses(sf), ...findMemberOrder(sf), ...naming].map((f) => ({ + ...f, + file, + })); + }), + }; + }, + ); +}; + +const isException = (f: Located): boolean => + f.rule === 'naming-convention' && + NAMING_EXCEPTIONS.some((e) => e.file === f.file && e.name === f.name); + +const report = (xs: readonly Located[]): string => + xs.map((f) => `${f.file}:${String(f.line)} ${f.name} — ${f.detail}`).join('\n'); + +describe('LINT-GAPS — class, casing e ordem de membros', () => { + const { trackedCount, unscanned, findings } = scan(); + + it('nenhuma classe no repositório', () => { + const offenders = findings.filter((f) => f.rule === 'no-class'); + assert.deepEqual(offenders, [], `Classe encontrada:\n${report(offenders)}`); + }); + + it('nomes seguem a convenção (fora de tests/)', () => { + const offenders = findings.filter((f) => f.rule === 'naming-convention' && !isException(f)); + assert.deepEqual(offenders, [], `Nome fora da convenção:\n${report(offenders)}`); + }); + + it('membros de interface e type literal na ordem padrão', () => { + const offenders = findings.filter((f) => f.rule === 'member-ordering'); + assert.deepEqual(offenders, [], `Membro fora de ordem:\n${report(offenders)}`); + }); + + it('toda exceção de nome ainda é necessária (allowlist não apodrece)', () => { + const stale = NAMING_EXCEPTIONS.filter( + (e) => !findings.some((f) => f.file === e.file && f.name === e.name), + ); + assert.deepEqual(stale, [], 'Exceção que não corresponde a nenhum achado — remover da lista'); + }); + + it('o git devolve fontes versionadas (guarda contra verde por lista vazia)', () => { + assert.ok( + trackedCount > 0, + '`git ls-files` não devolveu `.ts` algum — sem isso, os casos abaixo comparam conjuntos ' + + 'vazios e passam sem verificar nada.', + ); + }); + + it('todo `.ts` versionado entra na varredura (guarda contra verde por vacuidade)', () => { + assert.deepEqual( + unscanned, + [], + `${String(unscanned.length)} de ${String(trackedCount)} arquivo(s) versionado(s) ficaram ` + + `fora do programa do TypeScript:\n${unscanned.join('\n')}\n\n` + + 'Eles escapam do banimento de `class` e da convenção de nomes sem acusar. Ou o arquivo ' + + 'entra num glob de `include` do tsconfig.json, ou entra em IGNORED_PREFIXES com o motivo.', + ); + }); + + it('o `typescript` está em versão EXATA (ADR-0072 D4)', () => { + // Estes gates leem a AST pela API `typescript/unstable/*`. Uma faixa (`^7.0.2`) deixa a + // resolução trocar essa superfície sem decisão — e foi exatamente o que aconteceu: o pin + // estava afirmado no ADR e no `ts-ast.ts`, e o manifesto dizia `^7.0.2` até a revisão. + const manifest = JSON.parse(readFileSync(join(PROJECT_ROOT, 'package.json'), 'utf8')) as { + devDependencies?: Record; + }; + const spec = manifest.devDependencies?.['typescript'] ?? '(ausente)'; + assert.match( + spec, + /^\d+\.\d+\.\d+$/, + `typescript declarado como "${spec}" — fixar a versão exata`, + ); + }); +}); diff --git a/tests/cleanup/node-flags-not-redundant.test.ts b/tests/cleanup/node-flags-not-redundant.test.ts new file mode 100644 index 000000000..63edf6a8b --- /dev/null +++ b/tests/cleanup/node-flags-not-redundant.test.ts @@ -0,0 +1,168 @@ +/** + * NODE-FLAGS-NOT-REDUNDANTE — nenhum script do `package.json` passa flag que o Node 24 já dá. + * + * Molde: tests/cleanup/*.test.ts (varrem o fonte e exigem um estado desejado). + * + * DUAS flags que 45 dos 64 scripts carregavam foram medidas no runtime do `devEngines` (24.21.0) + * em 06/10/2026 e não faziam diferença: + * + * `--experimental-strip-types` O stripping é DEFAULT desde o Node 23.6 (backport em 22.18) — a + * prova no `--help` é existir `--no-strip-types` como negação. O + * repositório tem **zero** `enum` e **zero** `namespace`, então + * nada aqui precisaria de `--experimental-transform-types`. + * `--no-warnings` Existia para silenciar o `ExperimentalWarning` do stripping. Sem + * a flag, a execução de script real e de `node --test` emite + * **zero** linha em stderr. Mantê-la passou a significar engolir + * `DeprecationWarning` futuro em 45 comandos — o canal pelo qual + * o Node anuncia o que quebra na próxima major. + * + * ## `--enable-source-maps` NÃO entra nesta lista, e a correção é o registro mais útil deste + * ## arquivo + * + * A primeira versão deste gate também a proibia, com a justificativa de que "nenhuma das 10 + * dependências de produção publica `.js.map`". **Era falso, e por erro de medição:** o `find` + * rodou sem `-L`, e `node_modules/` é SYMLINK sob pnpm — a varredura nunca desceu no alvo. + * Medido de novo com `find -L`: `drizzle-orm` publica **444** arquivos `.js.map` e + * `fast-xml-parser` mais 4. + * + * O efeito é real e reproduzível num erro lançado dentro da lib: + * + * sem a flag: at getTableColumns (…/drizzle-orm/utils.js:109:15) + * com a flag: at getTableColumns (…/drizzle-orm/src/utils.ts:208:9) + * + * O raciocínio original — "o stripping preserva a posição no `.ts`" — está certo para o código + * DESTE repositório, e é justamente por isso que enganou: a flag nunca serviu a ele. Serve às + * dependências que publicam mapa, e `drizzle-orm` é a mais usada do projeto (183 imports). Por + * isso ela FICA em todo comando, e um gate que a proibisse travaria a volta de algo necessário. + * + * Este gate existe porque flag redundante não dá sintoma: não falha, não avisa e é copiada por + * imitação do script vizinho — foi assim que chegou a 45. Ver + * [ADR-0073](../../handbook/architecture/adr/0073-nvmrc-single-source-supersedes-0058-partial.md) + * para a disciplina de subida de runtime que torna o aviso de depreciação acionável. + * + * A varredura cobre o `package.json` **e** os demais pontos que invocam `node`, porque a flag + * morava neles também — `Procfile` e `.githooks/` entre eles. Cobrir só o manifesto deixaria a + * `rules/testing.md` prometendo por escrito uma garantia que o gate não dava. + */ + +import { describe, it } from 'node:test'; +import { strict as assert } from 'node:assert'; + +import { readSource } from '../support/source-scan.ts'; + +/** Flag → por que o Node 24 a tornou redundante, na mensagem que quem for mexer vai ler. */ +const REDUNDANT: Readonly> = { + '--experimental-strip-types': + 'o stripping é default desde o Node 23.6 (o `--help` lista `--no-strip-types` como negação) e ' + + 'o repo não tem enum nem namespace', + '--no-warnings': + 'sem o ExperimentalWarning do stripping não sobra aviso algum para silenciar — e a flag ' + + 'engoliria DeprecationWarning, que é como o Node anuncia o que quebra na próxima major', +}; + +/** + * Onde a flag morava, além do manifesto. São os pontos que INVOCAM `node`; documentação fica de + * fora porque `handbook/specs/` e `handbook/interviews/` são acervo datado, onde a citação da + * flag é registro histórico e não instrução. + * + * O critério de entrada é EXECUTAR o runtime, não citá-lo: `block-npm.sh` e + * `block-inline-interpreter.sh` falam de `node` e `pnpm` dezenas de vezes no texto de ajuda que + * imprimem, e nenhuma dessas linhas invoca coisa alguma. Lista por menção acusaria justamente os + * arquivos que explicam a norma — a mesma armadilha que o `codeLines` abaixo registra. + * + * ⚠️ A lista é mantida à mão, e o caso `todo ponto de invocação listado existe` só guarda contra + * entrada MORTA — nada guarda contra entrada AUSENTE. Os três hooks de `.claude/hooks/` entraram + * em 06/10 por terem sido encontrados numa revisão, não pelo gate. + */ +const INVOCATION_POINTS: readonly string[] = [ + 'package.json', + 'Dockerfile', + 'Procfile', + 'compose.yaml', + 'scripts/ci/test-integration.ts', + 'scripts/e2e/auth.sh', + 'scripts/e2e/collaborators.sh', + 'scripts/e2e/contracts.sh', + '.githooks/commit-msg', + '.claude/hooks/pre-commit-tombstone.sh', + '.claude/hooks/regen-inquiry-index.sh', + '.claude/hooks/pre-commit-typecheck.sh', + '.claude/hooks/stop-quality-gate.sh', + '.claude/hooks/prettier-write.sh', +]; + +const scripts = (): Readonly> => + (JSON.parse(readSource('package.json')) as { scripts: Record }).scripts; + +/** + * Linhas de CÓDIGO do arquivo — comentário fora. + * + * A distinção não é preciosismo: na primeira execução deste gate ampliado, `Dockerfile` e + * `scripts/ci/test-integration.ts` foram acusados porque **documentam** a remoção das flags em + * comentário. Um gate que varre por nome reprova justamente o arquivo que explica a norma — a + * armadilha que `tests/support/source-scan.ts` registra no próprio docstring, e que este gate + * repetiu por usar `includes` sobre o texto cru. + * + * Cobre as duas sintaxes dos pontos de invocação: `#` (Dockerfile, shell, Procfile, YAML) e `//` + * (TypeScript). O `package.json` não tem comentário e passa inteiro, como deve. + */ +const codeLines = (path: string): readonly string[] => + readSource(path) + .split('\n') + .filter((line) => { + const t = line.trimStart(); + return !t.startsWith('#') && !t.startsWith('//') && !t.startsWith('*'); + }); + +const usesFlag = (path: string, flag: string): boolean => + codeLines(path).some((line) => line.includes(flag)); + +describe('NODE-FLAGS — nenhum ponto de invocação passa flag que o Node 24 já dá', () => { + for (const [flag, why] of Object.entries(REDUNDANT)) { + it(`${flag} não aparece em ponto de invocação algum`, () => { + const offenders = INVOCATION_POINTS.filter((path) => usesFlag(path, flag)).sort(); + assert.deepEqual( + offenders, + [], + `${offenders.length} arquivo(s) passam \`${flag}\`: ${offenders.join(', ')}.\n` + + `Redundante porque ${why}.`, + ); + }); + } + + /** + * Entrada morta na lista é tão ruim quanto ponto não coberto: o arquivo some, o `readSource` + * estoura, e quem for mexer culpa o gate em vez da lista. A `rules/testing.md` afirma que a + * remoção vale para TODO comando do repositório — esta lista é o que sustenta a frase. + */ + it('todo ponto de invocação listado existe', () => { + const missing = INVOCATION_POINTS.filter((path) => { + try { + readSource(path); + return false; + } catch { + return true; + } + }); + assert.deepEqual( + missing, + [], + `caminho(s) na lista que não existem mais: ${missing.join(', ')}. Remover da lista ou ` + + 'corrigir o caminho — entrada morta faz o gate falhar por motivo errado.', + ); + }); + + /** + * Guarda contra verde por vacuidade. Sem ela, renomear `scripts` no manifesto (ou um erro de + * leitura que devolvesse `{}`) deixaria os casos acima passando por não encontrar nada — o modo + * de falha que `node-version-single-source.test.ts` já pagou uma vez. + */ + it('há scripts invocando `node` para varrer', () => { + const nodeScripts = Object.entries(scripts()).filter(([, b]) => b.startsWith('node ')); + assert.ok( + nodeScripts.length > 0, + 'nenhum script do package.json começa com `node ` — ou o manifesto mudou de forma, ou a ' + + 'leitura falhou, e os casos acima passariam sem verificar nada.', + ); + }); +}); diff --git a/tests/cleanup/node-version-single-source.test.ts b/tests/cleanup/node-version-single-source.test.ts index ba515035b..1c6d7a5d3 100644 --- a/tests/cleanup/node-version-single-source.test.ts +++ b/tests/cleanup/node-version-single-source.test.ts @@ -1,92 +1,254 @@ /** - * NODE-VERSION-SINGLE-SOURCE — os lugares que declaram a versão do Node concordam. + * NODE-VERSION-SINGLE-SOURCE — a versão do Node vive no `.nvmrc`, e as demais declarações derivam. * * Molde: tests/cleanup/*.test.ts (varrem o fonte e exigem um estado desejado). * Irmão direto de `supply-chain-settings.test.ts`, que faz o mesmo para o pnpm. * - * Norma ([ADR-0058](../../handbook/architecture/adr/0058-runtime-tracks-recommended-lts.md) §4): a - * versão do runtime NÃO vive em ADR — vive onde é verificável e executável, e esses lugares MUST - * concordar entre si. São três, com papéis diferentes: + * Norma: [ADR-0073](../../handbook/architecture/adr/0073-nvmrc-single-source-supersedes-0058-partial.md), + * que supersede parcialmente o [ADR-0058](../../handbook/architecture/adr/0058-runtime-tracks-recommended-lts.md) + * §2 e §4 — a §1 (acompanhar o LTS recomendado, por subida gradual) e a §3 (troca de tecnologia por + * inquiry) seguem integralmente vigentes. * - * `package.json` → `engines.node` o piso aceito - * `Dockerfile` → `FROM node:` o que roda em produção, pinado por digest - * CI workflows → `node-version` o que testa + * `.nvmrc` → a FONTE. major.minor.patch exato + * `devEngines.runtime.version` → derivada, exata (o pnpm baixa este runtime) + * `engines.node` → derivada, piso por major + * `Dockerfile` → `FROM node:` → derivada, major.minor (é o que a tag oficial publica) + * `LABEL image.base.name` → derivada, igual à tag do FROM + * `@types/node` → derivada, faixa do major + * workflows → `node-version-file: .nvmrc` → leem a fonte, não repetem o número * - * O que se cobra é o MAJOR, não o patch, e a distinção é deliberada: patch diverge por motivo - * legítimo (a imagem base sobe quando o mantenedor publica; o CI usa a linha), enquanto major - * divergente significa que produção roda um runtime que ninguém testou. Exigir patch idêntico - * produziria vermelho a cada release do Node — ruído que treina a ignorar o gate. + * ## Por que a granularidade difere, e por que isto NÃO é o afrouxamento que a versão anterior fazia * - * O que este gate NÃO cobre, e o ADR-0058 declara: se o major é o LTS RECOMENDADO. Isso exige - * consultar a rede, e o gate local é offline e determinístico por desenho — mesma razão pela qual - * `pnpm audit` vive no CI. Um repositório inteiro coerente numa versão EOL passa aqui. + * A versão anterior deste gate cobrava só o **major**, argumentando que exigir o patch produziria + * vermelho a cada release do Node. O argumento valia enquanto cada ponto carregava o número escrito + * à mão: três cópias, três cadências, vermelho a cada bump. + * + * Com `.nvmrc` como fonte, o custo desaparece — subir o runtime é editar um arquivo, e as derivadas + * que ainda escrevem o número (Dockerfile, devEngines) são conferidas contra ele. O que sobra de + * frouxo é só o que a natureza do alvo impõe: a tag Docker oficial não publica patch, e + * `engines.node`/`@types/node` são faixas por desenho. + * + * O preço de ter cobrado só o major está medido: o ADR-0058:41 registrou em 05/08/2026 que os três + * pontos divergiam (`>=24.0.0`, `node:24.15`, `node-version: '24'`) e que a §4 existia para tornar + * isso visível. O gate passou verde sobre essa divergência por dois meses — ela era de **minor**, e + * ninguém tinha mandado olhar o minor. Em 06/10/2026 dev e produção rodavam `24.16.0` e `24.15`. + * + * ## O que este gate NÃO cobre, e é declarado + * + * Se o major é o **LTS recomendado** — isso exige rede, e o gate local é offline e determinístico + * por desenho (ADR-0058 §4, ainda vigente nesta parte). Um repositório inteiro coerente numa versão + * EOL passa aqui. A pendência de CI agendado do ADR-0058 segue aberta. + * + * O **digest** do `FROM` também não: a tag é reconstruída upstream a cada patch de Debian, e um + * gate tag↔digest nasceria vermelho no primeiro rebuild. Digest errado falha o build com + * `manifest unknown` — ruidoso, na hora, sem precisar de teste. */ import { describe, it } from 'node:test'; import { strict as assert } from 'node:assert'; -import { readdirSync } from 'node:fs'; -import { join } from 'node:path'; +import { execFileSync } from 'node:child_process'; import { PROJECT_ROOT, readSource } from '../support/source-scan.ts'; -const WORKFLOWS = '.github/workflows'; +/** + * Workflows VERSIONADOS. `git ls-files` e não `readdirSync`: gate estrutural pergunta ao git, que + * responde a mesma coisa aqui e no runner (rule `testing.md`). O efeito colateral desejado é que + * workflow novo só entra na varredura quando staged — e aí a cobertura cresce sozinha. + */ +const workflowFiles = (): readonly string[] => + execFileSync( + 'git', + ['-C', PROJECT_ROOT, 'ls-files', '.github/workflows/*.yml', '.github/workflows/*.yaml'], + { encoding: 'utf-8' }, + ) + .split('\n') + .filter((l) => l.length > 0); -/** `>=24.0.0` → `24`. O piso declarado em `engines.node`. */ -const engineMajor = (): string | undefined => { - const pkg = JSON.parse(readSource('package.json')) as { engines?: { node?: string } }; - return /(\d+)\./.exec(pkg.engines?.node ?? '')?.[1]; -}; +type PackageManifest = Readonly<{ + engines?: Readonly<{ node?: string }>; + devEngines?: Readonly<{ runtime?: Readonly<{ name?: string; version?: string }> }>; + devDependencies?: Readonly>; +}>; -/** `FROM node:24.15-bookworm-slim@sha256:…` → `24`. O que roda em produção. */ -const dockerMajor = (): string | undefined => - /^FROM node:(\d+)\./m.exec(readSource('Dockerfile'))?.[1]; +const pkg = (): PackageManifest => JSON.parse(readSource('package.json')) as PackageManifest; -/** `node-version: '24'` / `node-version: 24` em cada workflow que o declara. */ -const workflowMajors = (): ReadonlyMap => { - const out = new Map(); - for (const f of readdirSync(join(PROJECT_ROOT, WORKFLOWS)).filter((n) => n.endsWith('.yml'))) { - const m = /node-version:\s*['"]?(\d+)/.exec(readSource(`${WORKFLOWS}/${f}`)); - if (m?.[1] !== undefined) out.set(f, m[1]); - } - return out; -}; - -describe('NODE-VERSION-SINGLE-SOURCE — produção roda o runtime que o CI testa', () => { - it('engines.node e o Dockerfile declaram o mesmo major', () => { - const engine = engineMajor(); - const docker = dockerMajor(); - assert.ok(engine !== undefined, 'engines.node não declara versão de Node'); - assert.ok(docker !== undefined, 'Dockerfile não declara `FROM node:`'); +/** A fonte: `major.minor.patch` exato, sem `v`, como o fnm e o `setup-node` esperam. */ +const NVMRC = readSource('.nvmrc').trim(); + +const PARSED = /^(\d+)\.(\d+)\.(\d+)$/.exec(NVMRC); +const MAJOR = PARSED?.[1]; +const MINOR = PARSED?.[2]; + +/** `FROM node:24.21-bookworm-slim@sha256:…` → `24.21`, exigindo o digest pin do ADR-0011. */ +const FROM_BASE = /^FROM node:(\d+\.\d+)-bookworm-slim@sha256:[0-9a-f]{64} AS base$/m; + +describe('NODE-VERSION-SINGLE-SOURCE — a fonte é legível', () => { + it('.nvmrc declara uma versão exata major.minor.patch', () => { + assert.ok( + PARSED !== null, + `.nvmrc contém ${JSON.stringify(NVMRC)}, que não é uma versão exata. Este arquivo é a fonte ` + + 'que o fnm lê no `cd` e que o `setup-node` resolve por `node-version-file` — uma faixa ' + + 'aqui devolve ao CI a liberdade de escolher sozinho o runtime, que é o defeito que o ' + + 'ADR-0073 fecha.', + ); + }); +}); + +describe('NODE-VERSION-SINGLE-SOURCE — package.json deriva do .nvmrc', () => { + it('devEngines.runtime fixa exatamente a versão do .nvmrc', () => { + const runtime = pkg().devEngines?.runtime; assert.equal( - docker, - engine, - `Dockerfile roda Node ${docker}.x e engines.node exige ${engine}.x — ` + - 'produção executaria um major que o repositório não declara suportar', + runtime?.name, + 'node', + 'devEngines.runtime.name deixou de ser `node` — a declaração que faz o pnpm baixar o ' + + 'runtime certo (`onFail: "download"`) perde o efeito em silêncio.', + ); + assert.equal( + runtime?.version, + NVMRC, + `devEngines.runtime.version é ${String(runtime?.version)} e .nvmrc é ${NVMRC}. O pnpm ` + + 'baixaria um runtime e o fnm outro, na mesma máquina.', ); }); - it('todo workflow que fixa node-version usa o mesmo major', () => { - const engine = engineMajor(); - const divergentes = [...workflowMajors()] - .filter(([, major]) => major !== engine) - .map( - ([file, major]) => - `${WORKFLOWS}/${file}: node-version ${major} (esperado ${String(engine)})`, - ) - .sort(); - assert.deepEqual( - divergentes, - [], - 'Workflow testando num major diferente do declarado — verde no CI deixa de significar ' + - 'verde em produção:\n' + - divergentes.join('\n'), + it('engines.node cobre o major do .nvmrc', () => { + assert.match( + pkg().engines?.node ?? '', + new RegExp(`>=\\s*${String(MAJOR)}\\.`), + `engines.node não cobre o major ${String(MAJOR)} do .nvmrc — o install passaria num runtime ` + + 'que ninguém testou.', + ); + }); + + it('@types/node acompanha o major do runtime', () => { + const types = pkg().devDependencies?.['@types/node']; + assert.ok(types !== undefined, '@types/node saiu de devDependencies'); + assert.match( + types, + new RegExp(`^[~^]?${String(MAJOR)}\\.`), + `@types/node está em ${types} e o runtime é ${NVMRC}. Tipo de uma major e runtime de outra ` + + 'deixa o typecheck aprovar API que não existe em execução — ou reprovar a que existe.', ); }); +}); - it('há workflows declarando node-version (guarda contra verde por vacuidade)', () => { +describe('NODE-VERSION-SINGLE-SOURCE — produção constrói do mesmo major.minor', () => { + it('o FROM da base usa a major.minor do .nvmrc e continua pinado por digest', () => { + const match = FROM_BASE.exec(readSource('Dockerfile')); assert.ok( - workflowMajors().size > 0, - 'nenhum workflow declara `node-version` — o gate passaria sem verificar nada', + match !== null, + 'não achei `FROM node:.-bookworm-slim@sha256:<64 hex> AS base` no Dockerfile. ' + + 'O digest pin é exigência do ADR-0011 (supply chain) — perder a forma é perder a ' + + 'reproducibilidade, não só a legibilidade.', + ); + assert.equal( + match?.[1], + `${String(MAJOR)}.${String(MINOR)}`, + `a imagem base é node:${String(match?.[1])} e o repo desenvolve em ${NVMRC}. A diferença ` + + 'entre dev e produção é exatamente a classe de defeito que só reproduz no ambiente que ' + + 'ninguém usa para desenvolver.', ); }); + + it('o LABEL base.name cita a mesma tag do FROM', () => { + const dockerfile = readSource('Dockerfile'); + const from = FROM_BASE.exec(dockerfile)?.[1]; + const label = /image\.base\.name="docker\.io\/library\/node:(\d+\.\d+)-bookworm-slim"/.exec( + dockerfile, + )?.[1]; + assert.equal( + label, + from, + `o LABEL anuncia node:${String(label)} e o FROM constrói de node:${String(from)}. O label é ` + + 'o que Trivy, Scout e Snyk leem para decidir quais CVEs se aplicam — divergente, ele ' + + 'manda o scanner auditar uma imagem que não está no ar.', + ); + }); +}); + +describe('NODE-VERSION-SINGLE-SOURCE — o CI lê a fonte em vez de repetir o número', () => { + const workflows = workflowFiles(); + + it('há workflow versionado para varrer (guarda contra verde por vacuidade)', () => { + assert.ok( + workflows.length > 0, + 'nenhum workflow veio do `git ls-files` — o gate passaria sem verificar nada. Arquivo novo ' + + 'precisa estar staged para este gate o ver.', + ); + }); + + /** + * Esta guarda é a que pegou a própria migração para `node-version-file`: a versão anterior do + * gate contava workflows com `node-version:` e, ao somarem zero, ela foi o único teste a falhar. + * Sem ela, a troca teria deixado o gate verde por não achar o que verificar. + */ + it('algum workflow usa actions/setup-node (guarda contra verde por vacuidade)', () => { + const withSetupNode = workflows.filter((f) => readSource(f).includes('actions/setup-node')); + assert.ok( + withSetupNode.length > 0, + 'nenhum workflow usa actions/setup-node — ou o CI deixou de instalar Node, ou a action ' + + 'mudou de nome e os casos abaixo passaram a não verificar nada.', + ); + }); + + /** + * O critério é QUEM RODA Node, não quem já declara `setup-node` — e a diferença não é teórica: + * `audit.yml` executava `corepack enable` + `pnpm audit` sem declarar runtime algum, caindo no + * Node pré-instalado da imagem do runner. É precisamente o "o CI escolhe sozinho" que o + * ADR-0073 §2 encerra, e um gate que varresse só quem tem `setup-node` seria cego justamente + * para o workflow em falta. + * + * O padrão cobre as QUATRO formas em que a invocação aparece, porque varrer só uma delas é + * aprovar por vacuidade quem usa as outras — e a versão anterior varria uma só, `run:` em linha + * própria. Media em 06/10: ela não casava nem `- run: pnpm install`, a forma mais comum, nem + * `run: |` com o comando na linha seguinte; `audit.yml` só foi pego porque, além dos três blocos + * escalares, tinha um `run: corepack enable` numa linha. Nenhum workflow de hoje depende da + * diferença (os 9 recebem o mesmo veredito nas duas versões): o buraco era latente, e o próximo + * workflow escrito com bloco escalar o tornaria vivo com o gate verde. + * + * O `\b` imediatamente após o prefixo opcional é o que mantém a precisão: `name: valida o pnpm`, + * `# pnpm install` e `uses:` não casam, porque neles o comando não abre a linha nem segue um + * `run:`. + */ + const RUNS_NODE = /^\s+(-\s+)?(run:\s*\|?.*)?\b(node|pnpm|npx|corepack)\b/m; + + it('todo workflow que roda Node declara o runtime por setup-node', () => { + const offenders = workflows + .filter((f) => { + const body = readSource(f); + return RUNS_NODE.test(body) && !body.includes('actions/setup-node'); + }) + .sort(); + assert.deepEqual( + offenders, + [], + `workflow(s) executando Node sem declarar a versão: ${offenders.join(', ')}. Sem ` + + '`setup-node` + `node-version-file: .nvmrc`, o job roda no Node pré-instalado do runner, ' + + 'que muda sem aviso e sem diff.', + ); + }); + + for (const workflow of workflows) { + const body = readSource(workflow); + if (!body.includes('actions/setup-node')) continue; + + it(`${workflow} resolve o runtime por node-version-file`, () => { + const literal = /^\s*node-version:\s*(.+)$/m.exec(body); + // `assert.ok` e não `assert.equal(literal, null)`: o segundo imprime o RegExpExecArray + // inteiro, e seu campo `input` é o workflow COMPLETO — 140 linhas de YAML enterrando a + // mensagem que diz o que fazer. Medido na primeira execução deste caso. + assert.ok( + literal === null, + `${workflow} fixa \`node-version: ${String(literal?.[1]).trim()}\` à mão. Um major solto ` + + 'flutua a cada release upstream e um valor exato vira a quarta cópia do número — as ' + + 'duas formas são o que o ADR-0073 proíbe. Trocar por `node-version-file: .nvmrc`.', + ); + assert.match( + body, + /^\s*node-version-file:\s*\.nvmrc\s*$/m, + `${workflow} usa actions/setup-node sem declarar \`node-version-file: .nvmrc\` — sem ` + + 'isso o runner cai no Node pré-instalado da imagem, que muda sem aviso.', + ); + }); + } }); diff --git a/tests/decisions/decision-records.test.ts b/tests/decisions/decision-records.test.ts index 67cfbccf7..b7622befe 100644 --- a/tests/decisions/decision-records.test.ts +++ b/tests/decisions/decision-records.test.ts @@ -227,7 +227,7 @@ for (const file of yamlFiles) { }); // Regra 1 do context/INDEX.md: o que já é mecânico não vira texto — mas tem de ser rastreável. - // Mecanismo é teste, rule semgrep, config eslint ou hook — qualquer um, desde que exista. + // Mecanismo é teste, rule semgrep, config do oxlint ou hook — qualquer um, desde que exista. it('enforced_by lista mecanismos que existem no repo', () => { assert.ok('enforced_by' in testability, 'campo enforced_by ausente'); const mechanisms = asStringArray(testability['enforced_by'], `${id}#enforced_by`); diff --git a/tests/etl/financial/writer.integration.test.ts b/tests/etl/financial/writer.integration.test.ts index ee0fb5620..b76861737 100644 --- a/tests/etl/financial/writer.integration.test.ts +++ b/tests/etl/financial/writer.integration.test.ts @@ -83,9 +83,9 @@ describe('FINANCIAL WRITER integration — legado → core-api destino', { skip: const doc1 = depara.find((d) => d['entity'] === 'document' && d['legacyId'] === 1); assert.ok(doc1); const cat = doc1['legacyCategorization'] as Readonly> | null; - assert.ok(cat !== null && cat['legacyCostCenterId'] === 10); + assert.ok(cat?.['legacyCostCenterId'] === 10); const inst = doc1['legacyInstallments'] as Readonly> | null; - assert.ok(inst !== null && inst['count'] === 1 && inst['sumCents'] === 50000); + assert.ok(inst?.['count'] === 1 && inst['sumCents'] === 50000); // Aprovação ÓRFÃ (F2): payable 2 é LANÇADO mas tem registro de aprovação. const orphans = depara.filter((d) => d['entity'] === 'orphan-approval'); diff --git a/tests/etl/orchestrate.fakes.ts b/tests/etl/orchestrate.fakes.ts index 48c22e597..ddaeab101 100644 --- a/tests/etl/orchestrate.fakes.ts +++ b/tests/etl/orchestrate.fakes.ts @@ -79,7 +79,7 @@ export const makeFakeEntityStore = ( }; // Store que falha SEMPRE no provision (para o caminho de erro de persistência → quarentena). -export const makeFailingEntityStore = (): LegacyEntityStore => ({ +export const makeFailingEntityStore = (): LegacyEntityStore => ({ findByLegacyId: () => Promise.resolve(ok(null)), provision: () => Promise.resolve(err('partners-etl-store-unavailable')), }); diff --git a/tests/etl/orchestrate.test.ts b/tests/etl/orchestrate.test.ts index 22bf0e80a..399fc8165 100644 --- a/tests/etl/orchestrate.test.ts +++ b/tests/etl/orchestrate.test.ts @@ -63,7 +63,7 @@ import * as UserRefVo from '#src/shared/kernel/user-ref.ts'; // Helpers de montagem de LegacyData + sink de quarentena coletor. // --------------------------------------------------------------------------- -const emptyRead = (): TableRead => ({ rows: [], failures: [] }); +const emptyRead = (): TableRead => ({ rows: [], failures: [] }); const legacyData = (over: Partial = {}): LegacyData => ({ financiers: emptyRead(), @@ -318,10 +318,7 @@ describe('PARTNERS-ETL-ORCHESTRATOR — quarentena (D12)', () => { const stores = makeStores(); const brokenStores: Stores = { ...stores, - suppliers: makeFailingEntityStore() as FakeEntityStore< - Supplier, - SupplierId - >, + suppliers: makeFailingEntityStore() as FakeEntityStore, }; const data = legacyData({ suppliers: { rows: [supplierRow({ id: 1 })], failures: [] }, @@ -411,10 +408,7 @@ describe('PARTNERS-ETL-ORCHESTRATOR — reason fiel ao erro de port (Obs.2)', () const stores = makeStores(); const brokenStores: Stores = { ...stores, - suppliers: makeFailingEntityStore() as FakeEntityStore< - Supplier, - SupplierId - >, + suppliers: makeFailingEntityStore() as FakeEntityStore, }; const data = legacyData({ suppliers: { rows: [supplierRow({ id: 1 })], failures: [] }, @@ -455,10 +449,7 @@ describe('PARTNERS-ETL-ORCHESTRATOR — reason fiel ao erro de port (Obs.2)', () const stores = makeStores(); const brokenStores: Stores = { ...stores, - suppliers: makeFailingEntityStore() as FakeEntityStore< - Supplier, - SupplierId - >, + suppliers: makeFailingEntityStore() as FakeEntityStore, }; const data = legacyData({ suppliers: { rows: [supplierRow({ id: 1 })], failures: [] }, @@ -483,7 +474,7 @@ describe('PARTNERS-ETL-ORCHESTRATOR — reason fiel ao erro de port (Obs.2)', () // RED hoje: `migrateUserRow` (orchestrate.ts :259-263) só passa // { legacyId, email, massApprove } ao provisionLegacyUser — descarta name/cpf/ // telephone (já presentes em `validated`) e o collaboratorRef já resolvido. -// Como `node --test --experimental-strip-types` NÃO type-checa, o teste CARREGA e +// Como o type stripping do Node NÃO type-checa (só apaga anotação), o teste CARREGA e // o RED vem das ASSERÇÕES (os campos chegam `undefined` ao auth-port hoje). O gate // `pnpm run typecheck` também fica RED (campos inexistentes em ProvisionLegacyUserInput). // --------------------------------------------------------------------------- diff --git a/tests/infra/migrate-boot-inversion.test.ts b/tests/infra/migrate-boot-inversion.test.ts index 87e9e94c7..fc4ed524d 100644 --- a/tests/infra/migrate-boot-inversion.test.ts +++ b/tests/infra/migrate-boot-inversion.test.ts @@ -52,10 +52,18 @@ describe('CORE-MIGRATE-BOOT-INVERT — CA-B3: e2e migram antes de subir o server for (const name of SCRIPTS) { it(`CA-B3: scripts/${name} executa o migrate antes de src/server.ts`, () => { const src = read(join('scripts', name)); - // Casa a INVOCAÇÃO real (`node ... --no-warnings