Skip to content

chore(release): promove a 1.0.0-rc.4 para a main — PF, Silo, ciclo de QA da borda e o toolchain - #1031

Merged
GabrielAderaldo merged 36 commits into
mainfrom
dev
Oct 6, 2026
Merged

GabrielAderaldo merged 36 commits into
mainfrom
dev

Conversation

@GabrielAderaldo

@GabrielAderaldo GabrielAderaldo commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Promove a dev (1.0.0-rc.4) para a main — que é produção neste repositório.

O que vai ao ar

⚠️ Mudança incompatível

Adicionado

Corrigido

Alterado

Por que isto importa agora

A main está em 1.0.0-rc.3, com fastify@5.12.1 e nodemailer@9.1.1 — o GitHub acusa 2 vulnerabilidades high na branch default. As correções existem desde o #1037 e só chegam à produção por aqui.

Medições

  • dev..main = 0 depois do update-branch: a main é ancestral da dev, nada de conteúdo existe só nela.
  • O update-branch alterou 0 arquivos — trouxe apenas o nó de merge da promoção anterior (a16e1deb), exigido porque a proteção da main tem strict: true.
  • CHANGELOG da dev: 4 seções de versão (rc.4, rc.3, rc.2, rc.1), com a rc.4 gerada do histórico por pnpm release:notes.

Depois do merge

Marcar a tag v1.0.0-rc.4 na main, conforme o rito.

Assisted-by: Claude-Code:claude-opus-5

GabrielAderaldo and others added 9 commits October 1, 2026 15:05
… sem razão social

Fecha #1022. O cadastro de Fornecedor aceita pessoa física (CPF), pagos por RPA.

- Domínio: `SupplierDocument` (CPF | CNPJ, no módulo, não no kernel) e identidade PF × PJ
  como union discriminada — na PF razão social e nome fantasia não existem no tipo. O tipo
  de pessoa é derivado do documento, nunca gravado. PF com os campos preenchidos é recusada
  (`supplier-*-not-allowed-for-pf`); branco conta como ausente.
- Persistência: migration 0020 (`cnpj` → `document`, índice `par_suppliers_document_idx`,
  `corporate_name`/`fantasy_name` nullable com CHECK amarrado ao tamanho do documento).
  Ausência é NULL só na borda; sem valor sentinela (ADR-0070).
- HTTP v1 aditivo: `document` + `personType`; `cnpj` segue como alias deprecated por um
  ciclo, na entrada e na resposta. Códigos `invalid-supplier-document` e `*-document-duplicate`.
- Busca por CPF, CSV "CPF/CNPJ", leitura de nota resolve CPF (`findSupplierIdByDocument`),
  ETL aceita CPF na coluna legada.
- Testes de favorecido CPF no CNAB (transferência, J-52, Pix); sem mudança de código lá.
- ADR-0070 supersede parcialmente o ADR-0031.

Assisted-by: Claude-Code:claude-opus-5-5
Os 20 comandos de hook do `.claude/settings.json` eram `${CLAUDE_PROJECT_DIR}/...` sem
aspas: num clone em "Área de trabalho" o `/bin/sh` partia o caminho no espaço e todo hook
falhava como "non-blocking" — Prettier pós-edição, bloqueios de Bash e o gate do Stop
deixavam de rodar em silêncio. Agora `"\"${CLAUDE_PROJECT_DIR}\"/..."`, a forma da doc.

Com os hooks de volta, apareceram mais três defeitos da mesma família:

- `pre-commit-typecheck.sh` guardava `pnpm --dir=${CORE_API_DIR}` numa string e a expandia
  sem aspas: os quatro gates do pre-commit morriam com `ENOENT ... lstat '/home/.../Área'`.
  Agora a chamada é direta, com `--dir` entre aspas.
- `stop-quality-gate.sh` reportava `pnpm: comando não encontrado` como QUATRO vermelhos.
  Shell de hook não carrega o nvm; ausência do pnpm agora é diagnóstico próprio ("gate
  não rodou"), que continua bloqueando, mas com a causa verdadeira.
- O fixture do `gate-blocker` montava `typecheck: <caminho do tsc> --noEmit` sem aspas no
  package.json temporário; o cenário GREEN falhava por ambiente, não por gate.

Assisted-by: Claude-Code:claude-opus-5-5
- `rehydrate` aplica só a regra do CHECK (PF ⟺ nomes NULL). Exigir nome não-branco na
  LEITURA derrubava o `list()` inteiro por uma PJ gravada com razão social `''` por fora
  do domínio — o CHECK aceita, e essa linha sempre reidratou.
- Borda: documento só alfanumérico. Só o tamanho deixava um CPF mascarado (14 caracteres)
  passar como CNPJ sem máscara, enquanto o CNPJ mascarado caía em 400.
- ETL: CPF ambíguo (os 11 dígitos com zeros à esquerda também formam CNPJ válido — CNPJ
  legado que perdeu os zeros) vai para quarentena, não para PF; e documento inválido volta
  a juntar o erro de razão social na mesma quarentena.
- `companyNamesOf` no domínio substitui o mesmo `switch` repetido em DTO, CSV, mapper e
  busca; tamanhos de CPF/CNPJ têm fonte única em `SupplierDocument`.
- Reverte a edição da linha de status do ADR-0031: ADR aceito não se edita; a supersessão
  fica registrada no ADR-0070. ADR-0070 registra o risco aceito do OCR resolver CPF.
- Diagnóstico de duplicatas e textos de teste apontam `par_suppliers.document`.

Assisted-by: Claude-Code:claude-opus-5-5
Os 20 comandos de hook do `.claude/settings.json` eram `${CLAUDE_PROJECT_DIR}/...` sem
aspas: num clone em "Área de trabalho" o `/bin/sh` partia o caminho no espaço e todo hook
falhava como "non-blocking" — Prettier pós-edição, bloqueios de Bash e o gate do Stop
deixavam de rodar em silêncio. Agora `"\"${CLAUDE_PROJECT_DIR}\"/..."`, a forma da doc.

Com os hooks de volta, apareceram mais três defeitos da mesma família:

- `pre-commit-typecheck.sh` guardava `pnpm --dir=${CORE_API_DIR}` numa string e a expandia
  sem aspas: os quatro gates do pre-commit morriam com `ENOENT ... lstat '/home/.../Área'`.
  Agora a chamada é direta, com `--dir` entre aspas.
- `stop-quality-gate.sh` reportava `pnpm: comando não encontrado` como QUATRO vermelhos.
  Shell de hook não carrega o nvm; ausência do pnpm agora é diagnóstico próprio ("gate
  não rodou"), que continua bloqueando, mas com a causa verdadeira.
- O fixture do `gate-blocker` montava `typecheck: <caminho do tsc> --noEmit` sem aspas no
  package.json temporário; o cenário GREEN falhava por ambiente, não por gate.

Assisted-by: Claude-Code:claude-opus-5-5
… Docker Hub

Fecha #1026. A MinIO apagou `minio/minio` e `minio/mc` do Docker Hub em 2026-09-11 e o
espelho quay.io/minio exige login: nenhuma tag nem digest do oficial baixa sem conta. O
`integration` falhava todo dia na main desde 2026-09-12 (storage, logo, photo → gate).

O compose passa a `pgsty/silo` + `pgsty/mc`, pinados por digest multi-arch. Fork mantido,
AGPLv3 como o MinIO, e drop-in: envs `MINIO_*`, secrets `*_FILE`, rotas `/minio/*`,
healthcheck e bootstrap inalterados. Escolhido entre seis hipóteses testadas em container
isolado contra as quatro suítes reais do CI (registro na #1026); ADR-0071 supersede
parcialmente o ADR-0019 só na fonte da imagem.

Assisted-by: Claude-Code:claude-opus-5-5
- compose.yaml volta ao MESMO número de linhas: os comentários novos deslocavam tudo abaixo
  do serviço `minio` e deixavam erradas as citações `compose.yaml:N` em integration.yml,
  ADR-0014.yaml, ADR-0064 e spec 033. O detalhe da troca vive no ADR-0071.
- Inventário do ADR-0019: C5 passa a `contradicted` na letra (a imagem não é mais a oficial
  minio/minio, por decisão do ADR-0071) e os `verify` voltam a achar as linhas; índice
  de ADRs anota "imagem superseded por 0071" na linha da 0019.
- Doc de paridade local: o comando de criar bucket usava `minio/mc`, que sumiu; passa ao
  `pgsty/mc` pinado, com `--entrypoint /bin/sh` (o entrypoint da imagem é `mc`).
- gate-blocker: fixture com espaço no caminho — prova inversa medida: sem as aspas do
  `--dir` o cenário GREEN quebra. Antes, a regressão voltaria sem teste vermelho.
- Gate do Stop sem pnpm: mensagem neutra quanto ao gerenciador de versão e manda escalar
  ao humano em vez de alterar a home; PATH só no log. Título "fallback npx" do pre-commit
  corrigido (não há fallback: falha fechado).
- Nome do teste de infra e exemplo do docker-compose-expert deixam de citar minio/mc e de
  usar placeholder inválido.

Fora do escopo, registrado: #1027 (gate do Stop pula com .ts de nome com espaço).

Assisted-by: Claude-Code:claude-opus-5-5
fix(infra): S3 de dev/teste passa ao fork Silo — minio/minio sumiu do Docker Hub
…-fisica

# Conflicts:
#	.claude/hooks/stop-quality-gate.sh
#	handbook/architecture/adr/README.md
…sica

feat(partners): fornecedor pessoa física (CPF) — SupplierDocument, PF sem Razão Social/Nome Fantasia
Copilot AI balanced review requested due to automatic review settings October 5, 2026 18:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@lekadecastro

Copy link
Copy Markdown
Contributor

⚠️ Front de produção ainda não lê fornecedor PF

O front em produção (main do web-app) não suporta fornecedor pessoa física. O supplier.schema.ts da main exige corporateName e fantasyName preenchidos, e a PF chega com null. Basta existir um fornecedor PF em produção para a lista de Fornecedores inteira cair em erro. Contratos e Lançar Documento também o mostrariam como "PJ".

O suporte ao fornecedor PF já está na develop (web-app#409, #412, #413), validado pela P.O. na homologação. A promoção para a main é o web-app#419.

Pedido

Até o web-app#419 entrar na main, não deixar nascer fornecedor PF em produção:

  • Não rodar a carga de fornecedores PF do legado em produção. Este PR diz que "o ETL passa a migrar fornecedor PF".
  • Não criar PF por API/seed em produção.

Sem fornecedor PF, a rc.4 é compatível com o front atual: para PJ, cnpj segue no contrato, como este PR já diz. O ideal é o web-app#419 entrar antes ou junto desta promoção.

🤖 Generated with Claude Code

@lekadecastro

Copy link
Copy Markdown
Contributor

Atualização: a promoção do front para a main agora é o web-app#420 (só o fornecedor PF: #409, #412, #413), que substitui o #419. O resto do comentário anterior segue valendo.

…a passar

Medidos ao rodar o oxlint em paridade 1:1 com o ESLint (ADR-0072): o código que
o ESLint aprova tinha 16 violações que só o oxlint enxerga. Nenhuma muda
comportamento, exceto a primeira.

- 4× no-deprecated: `.meta({ example })` do Zod, depreciado pelo zod-openapi,
  vira `examples: [...]`. ⚠️ Muda o OpenAPI gerado (`example` → `examples`,
  o formato do OpenAPI 3.1). Nenhum teste dependia do campo.
- 7× prefer-optional-chain: `x !== null && x.campo === v` → `x?.campo === v`;
  o compilador segue estreitando `x` para não nulo depois da comparação.
- 3× no-unnecessary-type-parameters: genérico usado uma vez vira `unknown` ou
  `never` — `TableRead<T>` é todo readonly, então a troca é covariante e segura.
- 2× prefer-readonly-parameter-types e 1× consistent-generic-constructors.

Os dois testes de integração alterados (writer.integration e
renumber-by-vigencia.drizzle-mysql) mudam só a asserção; ficam para o CI, que
roda contra MySQL.

Assisted-by: Claude-Code:claude-opus-5-5
…tra sem side-by-side

Implementa o ADR-0072 (que supersede a decisão de linter da Inquiry-0029 e o D2
do ADR-0067) e, com ele, o D1 e o D3 do ADR-0067, aceitos em 25/08 e nunca
levados ao código.

Lint:
- `oxlint 1.86.0` + `oxlint-tsgolint 7.0.2003`, versões exatas; `pnpm run lint`
  é `oxlint`, sem cache — 2036 arquivos com type-aware em ~2,3 s (o ESLint
  levava 30–32 s sem cache e 39–62 s no CI, onde o cache nunca era restaurado).
- `.oxlintrc.json` GERADO do `eslint --print-config` dos cinco contextos: 154 das
  158 regras com a mesma severidade, opções e exceções por pasta; todas as
  categorias do oxlint desligadas. Paridade provada por diferencial (222 de 224
  violações reais no mesmo arquivo e linha) e por 36 de 36 sondas de opção.
- As 4 sem equivalente: `no-octal` o compilador já recusa (TS1121); `class`,
  `naming-convention` e `member-ordering` viram gate de AST em
  `tests/cleanup/lint-gaps.test.ts`, sobre a API `typescript/unstable/*`
  concentrada em `tests/support/ts-ast.ts`. Conferido contra o ESLint: 46 de 46
  nomes idênticos em 2036 arquivos; fixtures do autoteste 15 de 15; prova
  invertida reprova as três. As exceções `__brand` e `__nonZeroMoney` saem das
  diretivas `eslint-disable` para a allowlist do gate, com motivo.

TypeScript:
- `typescript` 7.0.2, versão exata (os gates usam a API `unstable/`). Saem
  `eslint`, `typescript-eslint`, `eslint-config-prettier`, `@eslint/js` e
  `@typescript/native-preview`; sai o `eslint.config.js`. O Prettier fica.

Harness e registros que dependiam do arquivo removido:
- Hooks Stop e pre-commit apontam para o `.oxlintrc.json`; o CI perde o passo
  de cache do ESLint; ignores atualizados.
- `enforced_by` dos registros de decisão apontam o mecanismo REAL: ADR-0009 e
  ADR-0011 → `.oxlintrc.json`; ADR-0045 e ADR-0051 → `module-boundary.test.ts`;
  ADR-0037 e ADR-0047 → `[]`, porque o `eslint.config.js` nunca cobriu a
  fronteira que eles afirmavam.
- Skills que linkavam `eslint.config.js` passam a linkar o mecanismo atual.

Assisted-by: Claude-Code:claude-opus-5-5
…nismo

CLAUDE.md, seis rules, o README, o SCHEMA dos registros de decisão, docblocks de
gates e comentários de código afirmavam que o ESLint barrava `class`, casing e
libs proibidas. Depois do ADR-0072 quem barra é o oxlint ou o gate de AST
`tests/cleanup/lint-gaps.test.ts`, e cada texto passa a nomear o mecanismo real.

Uma afirmação já era falsa antes da troca e foi corrigida, não só renomeada: a
`domain.md` dizia que `class`/`this` eram barrados pelo `no-restricted-syntax`,
mas a config só tinha seletores de `class` — `this` nunca foi barrado.

Nos comentários de código ("o ESLint recusa X"), a regra citada é uma das 154
portadas para o oxlint; a afirmação continua verdadeira e só a ferramenta muda.

Assisted-by: Claude-Code:claude-opus-5-5
Aceito pelo Tech Lead em 2026-10-05. Supersede a decisão de linter da
Inquiry-0029 (§6, ESLint + `--cache`) e o D2 do ADR-0067 (alias do TS 6); o D1,
D3–D6 do 0067 seguem vigentes e agora implementados.

O que derrubou a 0029 foi medido, não opinado: o `--cache` do ESLint dá falso
verde em regra type-aware (mudar só o tipo de retorno em `a.ts` deixa um
`no-floating-promises` em `b.ts` passar com cache e falhar sem; a doc do
typescript-eslint confirma que o cache não acompanha dependência entre
arquivos) e nunca era restaurado no CI (`Cache not found`, lint de 39–62 s).

- O ADR registra decisões, provas (diferencial, sondas, nomes 46/46, fixtures,
  prova invertida), custos — gates sobre API `unstable/`, três regras `nursery`,
  a mudança no OpenAPI — e quando reavaliar.
- Índice de ADRs: linha do 0072; o 0067 vira "Accepted (D2 superseded por
  0072)", sem editar o ADR aceito.
- Inquiry-0029 → `superseded`, com aviso no topo; o corpo, que registra as
  medições de 2026-08-06, fica intacto. Índices regerados por `docs:index`.
- O agent typescript-language-expert deixa de se apresentar como TS 6 com
  roadmap via `native-preview`.

Assisted-by: Claude-Code:claude-opus-5-5
Achado da revisão do PR: o ADR-0072 (D4) e o cabeçalho de
`tests/support/ts-ast.ts` afirmam que o `typescript` está em versão exata —
porque os gates de `lint-gaps` leem a AST pela API `typescript/unstable/*` —,
mas o manifesto dizia `^7.0.2`. O `pnpm add --save-exact` manteve o estilo de
faixa da entrada que já existia, e nada conferia a afirmação.

O valor é corrigido e a afirmação vira asserção em
`tests/cleanup/lint-gaps.test.ts`: uma faixa no `typescript` reprova o gate
(prova invertida feita — `^7.0.2` acusa "fixar a versão exata").

Assisted-by: Claude-Code:claude-opus-5-5
O scripts/e2e/bruno-all.sh era o unico lugar que chamava bru run e estava muito desatualizado; foi substituido pela abordagem de QA + pen-test por arquivos .rest. Remove o script e todas as referencias: o npm-script test:integration:all, os READMEs, a rule api-collections.md (verify block + enforced_by), as clausulas do ADR-0038.yaml que apontavam para o script (enforced_by -> []), e os dois testes que o citavam (migrate-boot-inversion e block-bash-file-io).

Assisted-by: Claude-Code:claude-opus-4-8
…m contracts/:id)

z.uuid() do Zod v4 aceitava UUID bem-formado nao-v4 (nil, max, v1/v3/v5); o dominio rejeita via isUuidV4, e o handler de contracts nao mapeava contract-id-invalid, caindo no default 500 (viola a invariante nunca-500). Introduz um schema uuidV4() compartilhado em src/shared/http (z.uuidv4 estrito, com teste de paridade com o isUuidV4 do dominio) e troca as 11 ocorrencias de z.uuid() da borda, preservando os modificadores. Defesa em profundidade: mapeia contract-id-invalid / amendment-id-invalid / document-id-invalid para 400 no contracts. Efeito medido: UUID nao-v4 passa a devolver 400 uniforme em toda rota :id.

Assisted-by: Claude-Code:claude-opus-4-8
O upload de logo (POST /api/v1/programs/:id/logo) confiava no Content-Type declarado e nao conferia os bytes reais — aceitava conteudo arbitrario rotulado como imagem (provado ao vivo: HTML como image/png -> 200). As rotas-irmas (foto de usuario, documentos fiscais) ja checavam magic-byte; o logo ficou de fora. Adiciona imageMagicBytesMatch (jpeg/png/webp) inline no use case uploadProgramLogo, retornando logo-content-mismatch -> 422, consistente com o photo-content-mismatch da foto.

Assisted-by: Claude-Code:claude-opus-4-8
…e a 24.21.0

O repositório declarava a versão do Node em cinco lugares e rodava três versões
diferentes: `.nvmrc` e `devEngines` em 24.16.0, `FROM`/`LABEL` em 24.15, e quatro
workflows em `node-version: '24'` — presos a nenhum dos dois e flutuando a cada
release upstream. O gate existente passava verde porque cobrava apenas o major, e a
divergência era de minor; o próprio ADR-0058:41 registrara a divergência em 05/08 como
"observação, não norma".

O `.nvmrc` passa a ser a fonte única por ser o único arquivo que o `fnm` (no `cd`) e o
`actions/setup-node` (`node-version-file`) leem sem tradução humana. As seis derivadas
são conferidas por gate, cada uma na granularidade que o alvo permite.

- ADR-0073, que supersede PARCIALMENTE o ADR-0058 (§2 e §4). A §1 — acompanhar o LTS
  recomendado — e a §3 — troca de tecnologia por inquiry — seguem vigentes; o corpo do
  ADR aceito não foi editado, só o status no índice.
- `tests/cleanup/node-version-single-source.test.ts` refeito: de 3 pontos por major para
  6 por derivação, com guarda contra verde por vacuidade. Foi essa guarda, na versão
  anterior, que acusou a troca de `node-version` por `node-version-file`.
- Versão alinhada em 24.21.0 (ponta da linha 24). O digest da imagem base foi
  recalculado. O `pnpm-lock.yaml` acompanha porque o pnpm registra o runtime do
  `devEngines` — sem isso o `--frozen-lockfile` do CI recusaria.
- Os workflows deixam de declarar `node-version` e passam a ler o `.nvmrc`.

Medido em 06/10/2026 contra `nodejs/Release/schedule.json`: a linha 24 entra em
maintenance em 20/10/2026 e a 26 vira Active LTS em 28/10/2026. O salto ao 26 é PR
próprio, deliberadamente fora daqui — rodar Current em produção, e empilhar um major de
runtime sobre a migração de toolchain desta branch, são duas coisas que este commit não
faz.

Refs #1035

Assisted-by: Claude-Code:claude-opus-5
…antes

45 dos 64 scripts do `package.json` carregavam `--experimental-strip-types
--enable-source-maps --no-warnings`, e as três foram medidas no runtime do `devEngines`
(24.21.0) em 06/10/2026 — nenhuma fazia diferença:

- `--experimental-strip-types`: o stripping é DEFAULT desde o Node 23.6 (backport na
  22.18). A prova verificável é `--no-strip-types` existir como negação no `--help`. O
  repositório tem zero `enum` e zero `namespace` (garantido por `erasableSyntaxOnly`),
  então nada aqui precisaria de `--experimental-transform-types` no lugar.
- `--enable-source-maps`: o stripping troca tipo por espaço em branco, então a posição no
  `.ts` é preservada e o stack trace sai idêntico com e sem a flag (medido: mesmo
  `arquivo.ts:27:9`). E nenhuma das 10 dependências de produção publica `.js.map` — a
  flag não tinha alvo algum neste repositório.
- `--no-warnings`: existia para calar o `ExperimentalWarning` do stripping, que não é mais
  emitido. Sem ela, script real e `node --test` emitem zero linha em stderr.

A terceira é a que justifica o commit. As duas primeiras eram inertes; `--no-warnings`
estava ENGOLINDO `DeprecationWarning` em 45 comandos — 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 produção o mesmo valia por `NODE_NO_WARNINGS=1` no Dockerfile,
que sai junto: o aviso passa a chegar ao stderr do contêiner, logo ao log do ECS.

`tests/cleanup/node-flags-not-redundant.test.ts` impede o retorno, que aconteceria por
imitação do script vizinho — foi assim que chegou a 45.

Um gate quase quebrou em silêncio: `migrate-boot-inversion.test.ts` ancorava em
`'--no-warnings src/server.ts'` para distinguir a invocação real do `pkill -f` do
cleanup. Sem a flag, os dois `indexOf` iriam a -1. A âncora passa a ser `node` no início
da linha, que o `pkill` não casa. Uma flag sem função sustentava a asserção.

Fora do escopo, de propósito: ~70 citações em `handbook/specs/` e `handbook/interviews/`
ficam intactas — é acervo datado, e reescrever registro histórico é falsificá-lo.

Refs #1035

Assisted-by: Claude-Code:claude-opus-5
…ndante

A flag removida do código voltaria pela porta da frente: `.claude/` tinha 43 ocorrências
das três flags em 16 arquivos, e é ela que o agente lê ANTES de olhar o repositório. O
mapa de propagação estava visível em `git grep -c` ordenado — `package.json:45` →
`nodejs-runtime-expert.md:8` → `Procfile:6`: o manifesto espalhou para a instrução, que
ensinava a espalhar de novo.

O achado mais caro não eram os comandos, e sim duas afirmações FALSAS do
`nodejs-runtime-expert`, que numa sessão futura teriam me feito recusar esta limpeza:

- "`--experimental-strip-types` está em Active development em Node 24 (não Stable)" — é
  default desde a 23.6, e produção já dependia disso (o ENTRYPOINT da imagem não passa
  flag alguma).
- `@types/node` declarado como `^22.10.0`, quando o manifesto diz `^24.0.0`.

As duas foram corrigidas e o agente ganhou um bloco explícito proibindo as três flags,
com a razão medida de cada uma.

Também atualizados: `rules/testing.md` (carrega por path em todo teste), as skills
`nodejs-fs-scripter`, `nodejs-process-runner`, `ts-domain-modeler`, `tdd-strategist`,
`tdd-theorist`, `tdd-tutor`, `test-pyramid-engineer`, `ts-quality-checker` e os agentes
`cnab240-bradesco` e `docker-compose-expert`.

Preservado de propósito: o gatilho de ativação do `nodejs-runtime-expert` (alguém ainda
pode PERGUNTAR sobre a flag), `--enable-source-maps` na lista de flags de diagnóstico do
Node, e os ponteiros para `handbook/reference/nodejs/`, que descrevem a doc oficial — ela
documenta essas flags, e o ponteiro não é instrução de uso.

Refs #1035

Assisted-by: Claude-Code:claude-opus-5
… um minor fixo)

O `Dockerfile` declarava `# syntax=docker/dockerfile:1.10` e a ponta estável é `1.27.1`
(medida em 06/10/2026 no Docker Hub) — 17 minors atrás, sem receber correção desde que a
`1.11` saiu, e sem nada no repositório capaz de apontar isso.

A doc oficial é explícita, e é a fonte aqui:

  "docker/dockerfile:1 - kept updated with the latest 1.x.x minor and patch release.
   (…) 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/

Um minor fixo é a pior das três posições possíveis: não é imutável (recebe patches
`1.10.x`) nem atualizado (congelou na `1.11`). A doc só oferece duas coerentes — `:1` ou
uma versão exata com digest.

Por que isto NÃO contraria o digest pin do ADR-0011: 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. A imagem base segue pinada por
`@sha256`. A distinção está registrada em comentário no próprio Dockerfile, porque é o
tipo de coisa que alguém "corrige" de volta por coerência aparente.

O repositório tinha TRÊS versões divergentes do mesmo directive: `Dockerfile` em `1.10`,
`.claude/agents/docker-compose-expert.md` em `1.7` — instrução viva, mais velha que o
código — e `handbook/reference/docker/` em `:1`, que já era a cópia correta da doc
oficial. As duas primeiras foram alinhadas; a reference não foi tocada. O agente ganhou a
regra com o link, para o número não voltar.

Refs #1035

Assisted-by: Claude-Code:claude-opus-5
…u não seguia o symlink do pnpm

Achado da revisão do PR. O commit 95abe48 removeu as três flags afirmando 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/<dep>` é SYMLINK sob pnpm — a
varredura nunca desceu no alvo e devolveu zero para tudo.

Remedido com `find -L`: `drizzle-orm` publica **444** arquivos `.js.map` e
`fast-xml-parser` mais 4. O efeito é 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á correto para o
código DESTE repositório, e é 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).
Perder isso degradaria o stack trace de `serve`, dos workers e dos jobs em produção.

A flag volta a todos os pontos de invocação: `package.json` (45 scripts), `Procfile`,
`NODE_OPTIONS` do Dockerfile, `scripts/e2e/*.sh`, o spawn de `test-integration.ts` e os
4 testes que spawnam processo. As outras duas seguem removidas — a medição delas não
dependia de `node_modules` e foi reproduzida.

Os outros três achados da revisão, todos de alcance de gate:

- `node-flags-not-redundant` cobria só o `package.json`, enquanto a `rules/testing.md`
  prometia por escrito "todos os comandos do repositório". Passa a varrer os 12 pontos
  de invocação, com caso que falha se um caminho da lista deixar de existir. ⚠️ Na
  primeira execução ampliada ele acusou `Dockerfile` e `test-integration.ts`, que apenas
  DOCUMENTAM as flags em comentário — a armadilha uso × menção. Agora ignora linha de
  comentário (`#`, `//`, `*`). Prova invertida: `--no-warnings` injetado numa linha de
  código do `Procfile` é acusado.
- `node-version-single-source` só enxergava workflow que JÁ usava `setup-node`.
  `audit.yml` executava `corepack enable` + `pnpm audit` sem declarar runtime, caindo no
  Node pré-instalado do runner — o "CI escolhe sozinho" que o ADR-0073 §2 encerra. O
  gate passa a cobrar por QUEM RODA Node, e o `audit.yml` ganhou o passo.
- `migrate-boot-inversion` ancorava em `^\s*node src/…`, que a volta da flag quebraria.
  A âncora aceita as flags que houver entre `node` e o caminho.

Textos que repetiam a premissa errada foram corrigidos, não só renomeados:
`rules/testing.md`, `agents/nodejs-runtime-expert.md` e o comentário do Dockerfile. O
agente ganhou a regra operacional que faltava: **ao medir dentro de `node_modules` sob
pnpm, usar `find -L`**.

Refs #1035

Assisted-by: Claude-Code:claude-opus-5
…movida

ADR-0074, que supersede o ADR-0034 (adocao do @usebruno/cli) e o ADR-0038
(Bruno obrigatorio via CLI + autoria .bru).

O gatilho foi o critério do proprio ADR-0038: "um .bru escrito e nao executado
e cobertura ilusoria". Quando o runner unico (scripts/e2e/bruno-all.sh) caiu em
05/10 por estar desatualizado, nada o substituiu — e os 266 .bru viraram
exatamente o que aquele ADR proibia. Somado a isso, o formato guarda o
encadeamento em JavaScript (bru.setVar) e as assercoes em chai, o que exige o
runtime do Bruno para saber se o arquivo esta correto.

Hurl 8.0.1 resolve os dois pontos: [Captures] nativo (sem JavaScript),
--variables-file (o arquivo nasce SEM segredo), --report-junit para CI e
`hurlfmt --check` como gate de sintaxe que roda sem servidor. A colecao foi
convertida: 241 requests em 23 arquivos, 24 [Captures] e 77 [Asserts] traduzidos
do JS, hurlfmt verde nos 23.

BREAKING CHANGE: api-collections/ (266 .bru) e @usebruno/cli saem do repositorio.
A remocao da devDependency levou 317 pacotes, e com eles as DUAS excecoes de
supply-chain que o Bruno exigia (protobufjs em allowBuilds, semver@5.7.2||6.3.1
em trustPolicyExclude) — removidas depois de `pnpm why` provar que nenhuma outra
cadeia as trazia. Excecao de politica sobrevive a dependencia que a justificou
se ninguem varrer.

Os fixtures binarios de upload (sample.jpg/pdf) migram de api-collections/ para
tests/fixtures/http-edge/, versionado e neutro: as pastas de saida da conversao
sao gitignored e guardar fixture dentro delas tiraria o arquivo do git.

Limites medidos, nao supostos: [Captures] NAO cruzam arquivos (capture em a.hurl
+ uso em b.hurl, mesmo comando e --jobs 1, falha); a suite exige o seed E2E (8
perfis) e hoje roda 82/241, o resto parando em variavel indefinida; --jobs 1 e
obrigatorio porque --test paraleliza por default e o rate limit de login e
compartilhado. Zero 500 nos 82 requests executados.

As colecoes convertidas ficam fora do git por ora (uso interno): o .hurl e
versionavel quando virar gate, porque nao carrega segredo; o .http, nao, porque
so funciona com o token colado dentro do arquivo. O .gitignore tambem passa a
cobrir .cockpit/ (estado de workspace da IDE).

Registro historico (handbook/specs, tickets, CHANGELOG) NAO foi reescrito
(ADR-0057 §5) e ainda descreve o Bruno: era verdade quando foi escrito.

Assisted-by: Claude-Code:claude-opus-5
O ciclo de QA de 05/10 provou que dois usuarios podiam ser criados com o MESMO
cpf e e-mails diferentes — ambos 201. Como cpf e documento, isso e duplicacao de
identidade: `auth_user` tinha uniqueIndex em email e legacy_id, nenhum em cpf, e
`createUserByAdmin` so checava findByEmail.

A garantia passa a viver no banco (`auth_user_cpf_idx`, migration 0010). cpf
segue nullable e o InnoDB permite multiplos NULL num UNIQUE, entao quem nasce
sem perfil (register/OIDC) nao colide — propriedade coberta por teste.

Os DOIS adapters que inserem em auth_user distinguem o 1062 pelo nome do indice
na sqlMessage: `user-repository.drizzle` devolve `cpf-already-registered` (409 na
borda, em create e update) e `provisioned-user-store.drizzle` idem, para a ETL
poder tratar. Sem a distincao, dado duplicado se disfarcaria de falha de infra
(`provisioned-user-store-unavailable`) e o usuario legado NAO seria migrado —
defeito que a propria migration criava.

Em `provisionLegacyUser`, cpf duplicado DEGRADA para null + warning e re-tenta, a
mesma politica que ja valia para cpf invalido (Evans p.82, reconstituicao): um
campo de perfil opcional nao vale o registro inteiro. O id e gerado uma vez e
reusado no retry — reconstruir nao troca identidade.

Testes: o contrato do port ganha CA7 (duplicado -> erro nomeado), CA8 (dois NULL
coexistem) e CA9 (upsert do proprio registro nao e conflito), valendo para as
duas implementacoes; CA7 provado invertido (removida a checagem, acusa). O gate
de schema varre TODAS as migrations, nao a 0000, porque o indice nasceu na 0010.
`user-repository.drizzle.test` passa a honrar AUTH_DATABASE_URL com fallback no
helper canonico, como contracts e financial ja faziam.

Efeito colateral medido e corrigido: 18 testes de rota ficaram vermelhos
(`409 !== 201`) porque as fixtures reusavam um cpf fixo para criar N usuarios. A
correcao e a fixture respeitar o invariante novo, nunca afrouxar o assert:
`tests/support/synthetic-cpf.ts` deriva cpf valido por modulo 11 de um seed.
Gerar em vez de listar numeros e deliberado — fixture e o caminho por onde dado
de cadastro real entra num repositorio publico.

Verificado contra MySQL 8.4 real (nao so fake): replay das 7 migrations em banco
limpo, e o contrato do UserRepository com MYSQL_INTEGRATION=1 passando 11/11.

Assisted-by: Claude-Code:claude-opus-5
`.tmp/` e onde script de medicao/conversao tem de viver: no scratchpad da sessao
o ESM resolve pelo path do ARQUIVO e os imports do projeto dao MODULE_NOT_FOUND.
Mas o diretorio nao entra em tsconfig algum, e o projectService type-aware do
ESLint reprova com `was not found by the project service` — qualquer script
ad-hoc ali travava o `lint` e, com ele, o gate do hook Stop.

A regua ja existia para os irmaos da mesma categoria (descartavel, gitignored,
fora de tsconfig): `tools/bugs-scripts/**` e `tests/reports/**` ja estavam na
lista de ignores, com esse motivo escrito. `.tmp/` ficou de fora — existia num
lugar e faltava no outro.

Assisted-by: Claude-Code:claude-opus-5
… 3a UNIQUE da ETL

Tres correcoes em `src/` apontadas pela revisao da branch.

1. O nome do indice era string magica em tres arquivos. `AUTH_USER_UNIQUE_INDEX`
   (schemas/mysql.ts) passa a ser a fonte unica dos tres nomes, e os dois adapters
   a consomem. Nao e cosmetica: o errno 1062 nao diz QUAL unicidade quebrou — quem
   distingue e o nome do indice dentro da sqlMessage, e e por ele que o adapter
   escolhe entre erro nomeado, idempotencia e falha de infra. Com o nome escrito a
   mao em cada lugar, um `drizzle-kit generate` que o renomeasse passava no gate e
   quebrava a traducao EM SILENCIO: o 409 do cpf duplicado viraria 500 e a ETL leria
   dado duplicado como banco indisponivel.

   O gate novo (schema-hardening, CA10) cobra a PROPRIEDADE, nao a contagem: cada
   valor da constante existe em alguma migration, nenhum adapter repete o literal, e
   os adapters de fato consomem a constante (guarda contra verde por vacuidade). A
   assercao que fecha e a NEGATIVA, e ela foi provada invertida — reintroduzido o
   literal, o gate acusa.

2. `provisioned-user-store` tratava DUAS das tres UNIQUE que o INSERT alcanca. O
   `auth_user_email_idx` caia em `provisioned-user-store-unavailable`: dado duplicado
   disfarcado de falha de infra, o mesmo defeito que o commit anterior corrigiu para
   o cpf. Agora devolve `email-already-registered`, que PROPAGA (email nao se degrada
   — e obrigatorio e e a identidade do login), diferente do cpf, que o use case anula
   e re-tenta. O comentario do arquivo dizia "as duas de auth_user"; eram tres.

3. `upload-program-logo`: o `default` do switch de magic-byte passa a ser
   fail-CLOSED. Hoje e inalcancavel (a allowlist roda antes), mas protege o dia em que
   um MIME novo entrar na allowlist sem a assinatura — com `return true` aquele MIME
   aceitaria conteudo falsificado em silencio (CWE-434), que e o defeito que a funcao
   veio corrigir.

   ⚠️ A funcao irma em `auth/adapters/http/photo-upload.ts` FICA fail-open, de
   proposito: lá o magic-byte roda na BORDA e a allowlist so no use case, entao fechar
   o default trocaria `photo-type-unsupported` por `photo-content-mismatch` — mudanca
   de contrato observavel, nao endurecimento. A assimetria esta documentada nos dois
   arquivos; e ela a razao de as duas copias NAO terem sido fundidas num modulo
   compartilhado nesta passada: unificar exige alinhar a ORDEM primeiro.

Nao mexido, com motivo medido: `uuidV4()` em response schema. A revisao sugeriu que
contrariava o principio do proprio arquivo, mas o `git blame` mostra o oposto — o
comentario de `financial/.../schemas.ts` JA dizia "IDs proprios seguem z.uuid()" antes,
e o commit anterior so trocou por `uuidV4()`. A politica (refs estrangeiras nao
revalidam, ids proprios revalidam) e pre-existente e deliberada. Risco residual medido:
toda fonte de id e `randomUUID()`, os 16 UUID de seed das migrations sao v4 e o banco
local tem zero nao-v4.

Assisted-by: Claude-Code:claude-opus-5
GabrielAderaldo and others added 8 commits October 6, 2026 17:18
… verde

O `audit (produção — blocking)` estava vermelho com 16 vulnerabilidades (10 high)
em quatro pacotes. Nao era regressao deste PR: toda a mudanca anterior em
package.json foi a REMOCAO de uma devDependency. O audit so nao rodava na dev
desde 10/09, e as advisories sairam nesse intervalo — este PR foi o primeiro run
depois delas, e revelou o estado em vez de causa-lo.

- `fastify` 5.12.1 -> 5.12.5 (patch, dentro do major 5).
- `nodemailer` 9.1.1 -> 10.0.15 (major). O unico breaking do 10.0.0 e exigir
  Node.js >= 20; estamos em 24. `createTransport`, `sendMail` e `verify()` nao
  mudaram de assinatura, e e toda a superficie que o adapter usa.
- `fast-uri` piso 3.1.6 -> 3.1.7 (resolve 3.1.8) e `brace-expansion` 5.0.9 ->
  5.0.11 (resolve 5.0.12), nos overrides.

Os dois overrides eram a parte interessante: eles estavam SEGURANDO a versao
vulneravel, nao excluindo-a. O comentario do proprio pnpm-workspace.yaml previa
isso por escrito — "o piso fixado para a advisory do dia vira o teto quando sai a
proxima" — e se cumpriu pela QUINTA vez no fast-uri e pela QUARTA no
brace-expansion. Faixa (`>=X <4`) nao empurra ninguem: o piso e ele proprio uma
versao aceitavel, entao `@fastify/ajv-compiler` seguia resolvendo 3.1.6 mesmo com
o fastify em 5.12.5. Expulsar exige SUBIR o piso acima da vulneravel. O historico
de reincidencia fica no comentario; o piso vigente e o novo.

Verificacao, com a ressalva de onde ela alcanca:
- `pnpm run audit` -> "No known vulnerabilities found" (era 16).
- Gate completo verde: 11931 testes, fail 0.
- O boot do Fastify esta exercitado de verdade: 190 arquivos de teste montam o app
  por `fastify.inject`. O `createTransport` real do nodemailer 10 roda em
  `nodemailer-require-tls.test.ts`, nos dois branches (pool e nao-pool).
- NAO houve boot contra SMTP real (o OrbStack estava desligado e subir container
  exige OK do dono): o envio de ponta a ponta segue coberto so por fake.

⚠️ `@types/nodemailer` FICA em `^8`: nao existe `@types/nodemailer@10` — a ultima
publicada e 8.0.2. O nodemailer 10 migrou para TypeScript mas declara `types: null`
e manteve, de proposito, compatibilidade com o layout do @types (esta no changelog
do 10.0.0). Quem tentar "alinhar" os majors nao vai achar o pacote.

⚠️ Divida registrada, nao corrigida aqui: o adapter importa subpath profundo
(`nodemailer/lib/smtp-pool/index.js`). Esse caminho NAO existe mais no v10 — o
pacote publica so `dist/`, e o `exports` mapeia `./lib/*`. Funciona hoje porque sao
`import type`, apagados em runtime, e o tipo vem do @types. Virar import de valor
quebraria. Trocar para o especificador do `exports` e assunto proprio.

Assisted-by: Claude-Code:claude-opus-5
Ciclo de QA/pen-test da borda HTTP (05-06/10): fecha os tres achados no codigo e troca a ferramenta da suite de colecoes.

- fix(auth): unicidade de CPF em auth_user (F7). UNIQUE auth_user_cpf_idx na migration 0010; os dois adapters que inserem em auth_user distinguem as TRES unicidades pelo nome do indice no errno 1062 — legacy_id (idempotencia), cpf (409 na borda; a ETL degrada e re-tenta) e email (propaga). Nome do indice em fonte unica (AUTH_USER_UNIQUE_INDEX), com gate que cobra a ausencia do literal nos adapters.
- fix(http): uuidV4 estrito na borda. z.uuid() do Zod v4 aceitava UUID nao-v4 e o dominio rejeitava, caindo no default 500 — violacao da invariante "nunca 500". Schema compartilhado em src/shared/http com teste de paridade contra o VO; 400 uniforme em toda rota :id.
- fix(programs): magic-byte no upload de logo (CWE-434). O default do switch e fail-closed aqui porque a allowlist roda antes; a funcao irma do auth FICA fail-open de proposito — la a ordem e inversa, e fechar trocaria o codigo de erro da API.
- feat(api-collections)!: Hurl substitui o Bruno (ADR-0074, supersede 0034 e 0038). api-collections/ (266 .bru) e @usebruno/cli removidos: -317 pacotes e as duas excecoes de supply-chain que o Bruno exigia. 241 requests convertidos em 23 arquivos.
- fix(deps): audit de producao zerado. fastify 5.12.1->5.12.5, nodemailer 9.1.1->10.0.15, e os pisos dos overrides de fast-uri e brace-expansion subidos — eles SEGURAVAM a versao vulneravel em vez de exclui-la, exatamente como o comentario do pnpm-workspace.yaml previa.

Verificacao: 20/20 checks verdes. Gate completo (11931 testes, fail 0) em cada commit, pelo hook de pre-commit. Contra MySQL 8.4 real: replay das 7 migrations em banco limpo e o contrato do UserRepository com MYSQL_INTEGRATION=1 (11/11), inclusive a propriedade de multiplos NULL no UNIQUE que um fake so imita. /security-review: zero vulnerabilidades novas.

Limites declarados: a suite Hurl NAO e gate (exige o seed E2E; roda 82/241) — a borda esta sem gate E2E desde 05/10, quando o runner do Bruno caiu, e isso e anterior a troca de ferramenta. A migration 0010 aborta se houver CPF duplicado pre-existente, e o replay em banco limpo e cego para isso: pre-voo necessario em ambiente com dado, risco assumido pelo dono. Registro historico (handbook/specs, tickets, CHANGELOG) nao foi reescrito (ADR-0057 §5).

Follow-up: #1036 (ETL degrada CPF duplicado sem quarentenar).
A branch do toolchain estava parada à espera da dev. Merge, não rebase: ela está
publicada e tem o PR #1035 aberto.

Cinco conflitos, dos treze arquivos que os dois lados tocaram:

- `scripts/e2e/bruno-all.sh` (delete/modify) — remoção aceita. O Bruno saiu do
  projeto inteiro pelo ADR-0074, e o runner não se ressuscita.
- `eslint.config.js` (delete/modify, invertido) — remoção aceita. A branch retira
  o ESLint pelo ADR-0072; a dev havia acrescentado `.tmp/**` aos ignores. A entrada
  foi portada para `ignorePatterns` do `.oxlintrc.json`, ao lado dos irmãos de mesma
  categoria (`tools/bugs-scripts/**`, `tests/reports/**`): descartável, gitignored,
  fora de qualquer tsconfig. Sem ela, script ad-hoc em `.tmp/` volta a travar o lint.
- `package.json` — os dois lados removeram coisas diferentes e a união remove ambas:
  sai `test:integration:all` (dev, apontava para o runner do Bruno) e sai
  `@usebruno/cli` (dev), sai `@typescript/native-preview` (branch, ADR-0072).
  Preservadas as flags que o TS 7 tornou redundantes, já retiradas pela branch.
- `pnpm-lock.yaml` — lado da dev aceito e regenerado com `pnpm install`, como manda
  o lockfile. Coexistem as deps do toolchain (oxlint, oxlint-tsgolint, typescript
  7.0.2) e o que a dev trouxe: fastify 5.12.5, nodemailer 10.0.15, zero `usebruno`,
  zero `typescript-eslint`, zero `native-preview`.
- `handbook/architecture/adr/README.md` — conflito aditivo: 0072 e 0073 da branch,
  0074 da dev. Os três preservados em ordem; 0034 e 0038 seguem `Superseded by 0074`.

Um vermelho veio do merge e foi corrigido aqui: `node-flags-not-redundant.test.ts`
listava `scripts/e2e/bruno-all.sh` em `INVOCATION_POINTS`, e o arquivo deixou de
existir. As três falhas tinham essa causa única — o `readSource` estourando ENOENT
derrubava junto os dois testes que apenas iteram a mesma lista, que é exatamente o
cenário descrito no docstring do gate. Nenhum dos dois lados produz esse vermelho
sozinho: a lista é artefato do ADR-0072, o arquivo foi removido pelo ADR-0074.

Gate verde: typecheck, format:check, lint (1017 arquivos, 154 regras, 0 erros) e
11963 testes com fail 0, 20 skipped.

Assisted-by: Claude-Code:claude-opus-5
… vacuidade

Nenhum defeito de correção no fonte de produção. O que a revisão achou foram duas
guardas que passariam vazias e duas inconsistências de registro.

`node-version-single-source`: o padrão `RUNS_NODE` varria uma única forma de
invocação — `run:` em linha própria. Medido em 06/10, ele não casava nem
`- run: pnpm install`, que é a forma mais comum em YAML, 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. Passa a cobrir as quatro formas. Provado
invertido contra caso sintético: o padrão antigo deixa passar três das quatro, o novo
acusa as quatro e segue ignorando comentário YAML, `uses:` e menção em prosa. Os 9
workflows de hoje 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.

`lint-gaps`: a guarda contra vacuidade era `scanned > 1500`, um limiar. Com 2041
arquivos, dezenas poderiam escapar sem derrubar o número — e escapam de verdade os
`.ts` versionados que caem fora dos globs de `include` do tsconfig, porque
`trackedTs()` lista `src tests scripts db` enquanto o programa só entrega
`db/drizzle/**`. Contagem não é a propriedade: passa a afirmar que nenhum arquivo
versionado fica de fora, listando quais quando houver. A guarda contra lista vazia do
git virou caso próprio, senão a comparação de conjuntos vazios passaria sozinha.

`integration.yml`: a remoção das flags redundantes levou junto `--enable-source-maps`,
que não é redundante no Node 24 — as redundantes eram `--experimental-strip-types` e
`--no-warnings`. Todos os outros pontos de invocação a mantêm. Sem ela, um stack trace
do próprio runner de integração aponta posição no `.js` empacotado.

`compose.yaml`: o comentário afirmava "Sem NODE_OPTIONS" enquanto o `Dockerfile:128`
define `NODE_OPTIONS="--enable-source-maps"`, herdado por todos aqueles serviços.
Registro que descreve o contrário do código.

`node-flags-not-redundant`: `INVOCATION_POINTS` não listava três hooks que executam o
runtime (`pre-commit-typecheck.sh`, `stop-quality-gate.sh`, `prettier-write.sh`), onde
as flags poderiam voltar sem vermelho. O critério de entrada é executar, não citar:
`block-npm.sh` e `block-inline-interpreter.sh` mencionam `node` e `pnpm` dezenas de
vezes no texto de ajuda e não invocam nada. Fica registrado no docstring que a lista é
manual e que nada guarda contra entrada ausente — só contra entrada morta.

Gate: typecheck, format:check e lint verdes; 11964 testes, fail 0, 20 skipped.

Assisted-by: Claude-Code:claude-opus-5
chore(toolchain): lint vira oxlint type-aware e o TypeScript 7 entra sem side-by-side (ADR-0072)
…hain

Traslado do #1030 para branch limpa, partindo da `dev`. A branch anterior recebia a
`dev` por merge, e é isso que produzia o CHANGELOG defasado: o gerador percorre
`git log --first-parent`, e numa branch que MERGEIA a `dev` ela é o segundo pai —
tudo que ela acumulou fica invisível, escondido atrás de um merge commit que o
gerador classifica como "não classificado". Medido em 06/10: a partir da branch
antiga o `--first-parent` achava 2 PRs; a partir da `dev`, acha os 4. `update-branch`
no #1030 teria piorado, porque também é merge. A branch de release precisa ser
descendente LINEAR da `dev`.

`release-notes`: entrega consolidada escreve as mudanças como bullets convencionais,
e o `CONVENTIONAL` exige o tipo no início da linha — o `- ` o empurrava e o merge
inteiro era descartado. O #1037 tem cinco bullets, entre elas um
`feat(api-collections)!`: a rc.4 omitia uma quebra de contrato. `parseEntry` passa a
devolver uma LISTA e emite uma entrada por bullet; sem bullets, segue valendo um
merge, uma entrada. O marcador de lista é obrigatório no padrão novo, senão todo
merge comum renderia duas vezes — coberto por caso próprio.

Efeito medido: de 3 entradas e 1 não classificado para **8 entradas e 0 não
classificados**, com o breaking em seção própria.

⚠️ O gerador reescreve o CHANGELOG inteiro com a versão corrente e apaga as
anteriores (#940). As seções rc.3, rc.2 e rc.1 foram recolocadas à mão depois de
cada geração. O defeito continua aberto — aqui ele foi contornado, não corrigido.

Gate: typecheck, format:check e lint verdes; 11966 testes, fail 0, 20 skipped.

Assisted-by: Claude-Code:claude-opus-5
…from-dev

chore(release): 1.0.0-rc.4 — PF, Silo, ciclo de QA da borda e o toolchain
@GabrielAderaldo GabrielAderaldo changed the title chore(release): promove a 1.0.0-rc.4 para a main — o fornecedor pessoa física chore(release): promove a 1.0.0-rc.4 para a main — PF, Silo, ciclo de QA da borda e o toolchain Oct 6, 2026
…payload

O `typecheck + format + lint + test` do #1031 ficou vermelho em
`post-compact-rules-reminder.test.ts:201` com `spawnSync bash EPIPE`, e o processo
tinha saído com `status: 0`. Parecia flake de CI — não era.

Causa: quem invoca o hook escreve o payload JSON no stdin dele. O
`post-compact-rules-reminder.sh` tinha o `exit 0` da falta do log ANTES do
`payload=$(cat)`: ele saía sem consumir o stdin, e o escritor levava EPIPE. O sintoma
é intermitente porque o buffer do pipe (64 KB) absorve a escrita quando o payload é
pequeno e o processo sai rápido. Medido em 06/10:

| payload | resultado |
| --- | --- |
| ~60 B e ~1 KB | 5 de 5 OK |
| ~70 KB e ~500 KB | 5 de 5 EPIPE |

O CI falhou com ~60 bytes porque o runner deixou o script sair primeiro. Era corrida,
não aleatoriedade — e foi isso que a disfarçou de vermelho de infraestrutura.

A régua não estava num lugar só: dos 4 hooks que leem stdin, **3** saíam antes de
drenar (`post-compact-rules-reminder.sh`, `logbook.sh`, `log-instructions-loaded.sh`).
Nos três, a leitura passa para antes de qualquer `exit` — inclusive antes do `cd`, que
também pode falhar e sair. Varredura depois da correção: nenhum.

O gate que trava a propriedade é `tests/cleanup/hooks-drain-stdin.test.ts`, com payload
de 128 KB — acima do buffer, que é o que o torna determinístico. Payload pequeno
aprovaria os hooks defeituosos, exatamente como o teste existente fazia.

⚠️ A PRIMEIRA versão deste gate passou 4 de 4 com o `logbook.sh` revertido ao padrão
defeituoso: ela usava só um cenário — `CLAUDE_PROJECT_DIR` num diretório vazio —, e
nele o `cd` do `logbook.sh` FUNCIONA, então o hook não sai cedo e o defeito não
aparece. Media a fronteira errada para dois dos três. O gate roda dois cenários:

| cenário | `cd` | post-compact | logbook · log-instructions |
| --- | --- | --- | --- |
| diretório vazio | funciona | EPIPE | ok (não sai cedo) |
| diretório inexistente | falha | EPIPE | EPIPE |

Provado invertido com o segundo cenário no lugar: revertendo o `logbook.sh`, o gate
acusa `logbook.sh — projeto inexistente`, e só esse caso.

Gate: typecheck, format:check e lint verdes; 11973 testes, fail 0, 20 skipped.

Assisted-by: Claude-Code:claude-opus-5
fix(harness): hook que sai cedo deixa de dar EPIPE em quem escreve o payload
@GabrielAderaldo

Copy link
Copy Markdown
Contributor Author

⏸️ Aguardando aprovação da P.O. — não mergear.

Decisão do dono em 06/10: os PRs param na dev; a main só recebe após aprovação da P.O. Este PR fica aberto e pronto, mas não deve ser mergeado até lá.

Estado técnico, para quando a aprovação vier:

⚠️ Enquanto isso não entrar, a branch default segue com fastify@5.12.1 e nodemailer@9.1.1 — as 2 vulnerabilidades high que o Dependabot acusa. As correções existem na dev desde o #1037 e chegam à produção só por aqui.

Depois do merge, falta marcar a tag v1.0.0-rc.4 na main.

Assisted-by: Claude-Code:claude-opus-5

@GabrielAderaldo
GabrielAderaldo merged commit e6bc7e3 into main Oct 6, 2026
22 checks passed
@lekadecastro

Copy link
Copy Markdown
Contributor

Front: o web-app#420 entrou na main em 07/10 (ab7005a74). A main do front agora lê e cadastra fornecedor PF. Quando esse build estiver implantado em produção, o pedido do comentário anterior (não criar e não migrar fornecedor PF em produção) deixa de valer. Até lá, ele continua valendo.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants