Skip to content

chore(release): integra a dev na main — o manifesto de workloads, o env gerado dele e o fechamento de issue na dev (#1051, #1060, #1064) - #1062

Merged
GabrielAderaldo merged 16 commits into
mainfrom
dev
Oct 15, 2026
Merged

GabrielAderaldo merged 16 commits into
mainfrom
dev

Conversation

@GabrielAderaldo

@GabrielAderaldo GabrielAderaldo commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Promove a dev para a main — que é produção neste repositório. 6 unidades de entrega desde o
#1046, nenhuma delas de release: a main
já declarava 1.0.0-rc.4 desde o #1031, e a tag
v1.0.0-rc.4 foi criada em
15/10 sobre o commit que a promoveu (e6bc7e30), fechando o passo 4 do rito do ADR-0066 — que havia
ficado pendente por nove dias, com a main declarando uma versão sem tag nem release
correspondente.

O que vai ao ar

Adicionado

Corrigido

Alterado e interno

Medições

diff main...dev 46 arquivos, +5717 / −480
src/modules/ tocado 4 arquivos. Os dois de CNAB são só TSDoc (+3/−3 e +5/−4, zero linha fora de comentário). Os dois de notifications são aditivos: a função describeEmailConfigError e o seu reexport na public-api.
src/workers/ 1 arquivo (runner/specs.ts, +9/−1) — passa a chamar a função nova no boot
migrations nenhuma
dev..main zerado — a main é ancestral da dev

Nota de convenção: a mensagem de boot vai SEM acentuação, de propósito

A função nova describeEmailConfigError emite as mensagens sem acentuação
(EMAIL_PROVIDER invalido, nao e endereco valido). Isso segue convenção do repositório,
declarada em cinco arquivos de configuração de boot:

Arquivo Razão declarada
src/shared/persistence/module-driver-config.ts:33 "Mensagens em PT sem acentuacao… saem em stderr no boot, antes de qualquer garantia de encoding"
src/modules/financial/adapters/van/van-s3-config.ts:24 "o stderr do boot antecede qualquer garantia de encoding do coletor de log"
src/modules/auth/adapters/http/jwt-key-config.ts:16 idem
src/modules/contracts/adapters/storage/s3-config-aws.ts:67 idem
src/modules/programs/adapters/http/logo-storage-config.ts:22 "como as dos dois precedentes de boot"

Não conflita com a tabela de idioma do CLAUDE.md: ela exige acentuação completa em
documentação, diálogo e commit, e apenas PT-BR em string ao humano.

O que de fato divergiu

src/workers/runner/specs.ts:283 monta o prefixo com acento e concatena a mensagem sem:

email-dispatch: config de e-mail inválida — EMAIL_PROVIDER invalido "sendgrid"
└── com acento (specs.ts:283)              └── sem acento (email-config.ts:98)

O prefixo acentuado é preexistente — a linha substituída já dizia inválida. O que nasce neste
range é a concatenação das duas convenções numa saída só.

E a razão da convenção não está declarada no email-config.ts: o TSDoc dele cita a régua da irmã
describeVanS3ConfigError apenas no eixo de segurança (CWE-532/CWE-117), nunca no de encoding.
Quem abrir o arquivo não acha o motivo — e foi o que aconteceu na preparação deste PR, onde a
ausência de acento foi lida como defeito até o arquivo vizinho ser aberto. Nenhum gate cobre a
convenção
, e ela depende de cinco comentários que não se citam entre si.

Nada disto bloqueia a promoção. Não há mudança de comportamento, e escolher qual lado alinhar —
prefixo sem acento, ou convenção revista — é decisão de ADR, não conserto de passagem.

Nota sobre o #1064, que muda o significado deste PR

Com o #1064 na dev, issue passa a fechar ao entrar na dev, e não mais ao chegar na main.
A consequência para este PR é direta: ele não fecha issue nenhuma — nunca fechou, porque o
GitHub só fecha automaticamente na branch default, mas agora isso é por desenho e está mecanizado.

O que este PR passa a ser, então, é exatamente o que o conceito novo diz: o ajuntamento que vai à
produção. Quem quiser saber o que está no ar olha a release; quem quiser saber o que está integrado
olha o backlog.

Portão

A promoção dev → main aguarda aprovação da P.O. (decisão de 06/10/2026): ela deixou de ser
passo automático da cadeia. Este PR fica aberto e verde até lá, como o #1031 ficou.

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

GabrielAderaldo and others added 13 commits October 7, 2026 13:46
Traslada o commit 0f1e90f5 da branch `chore/oxlint-ts7-migration`, que nunca
virou PR, para branch nascida da `dev`. Era o único conteúdo daquela frente que
não existe em nenhum outro lugar: `git ls-tree origin/dev -- src/deploy`
devolvia vazio, e nem a branch irmã `docs/tsdoc-contracts-financial` nem o
`backup/pre-split-2026-09-04` o têm.

Um assunto só. A toolchain, o TSDoc e o bump de dependência daquela branch
ficam de fora — o ADR-0072:35 cita as duas branches nominalmente como o
contraexemplo de misturar assuntos, e este PR é feito do avesso daquilo.

O que veio do commit original:

- `src/deploy/workloads.ts` — o manifesto das unidades executáveis.
- `tests/cleanup/workloads-manifest.test.ts` — o gate que o cobra.
- `handbook/inquiries/0038-fim-do-compose-como-modelo-de-execucao.md`.
- `.claude/rules/jobs-and-workers.md` — aponta para o manifesto.

O que foi reescrito contra o estado atual, e não trasladado:

- O bloco `## Inquiry-0038` do `PERGUNTAS-EM-ABERTO.md` e a linha dela na tabela
  de visão geral. O `inquiry-hygiene` cobra bloco próprio para toda inquiry
  `open`/`blocked` e correspondência entre a contagem da tabela e as caixas do
  bloco; as duas questões vêm da §9 e da §4 da própria inquiry.
- As contagens geradas do `INDEX.md` e do `PERGUNTAS-EM-ABERTO.md`, por
  `pnpm run docs:index`. A branch de origem trazia "14 de 37" escrito à mão,
  número correto quando havia 36 inquiries; hoje são 38, e o gerador resolve.

Corrige, no arquivo já tocado, o rodapé que afirmava que nenhum gate cobre o
checklist — `tests/cleanup/inquiry-hygiene.test.ts` cobre, e foi ele que barrou
a primeira versão deste commit.

Gate verde nos quatro: typecheck, format:check, lint e 12.009 testes com zero
falhas.

Assisted-by: Claude-Code:claude-opus-5
…rguntar ao git

Achados da revisão do diff, os quatro mecânicos. Os cinco de conteúdo do
manifesto viraram issues (#1047, #1048, #1049, #1050) — mexer neles é decidir
a semântica do manifesto, não consertar o gate.

1. O gate de variável-fantasma NUNCA podia falhar. `src/deploy/workloads.ts`
   vive dentro de `src/`, a varredura concatenava `src/` inteiro, e cada
   variável aparece no manifesto como literal `'NOME'` — então
   `src.includes("'NOME'")` era satisfeito pelo próprio arquivo sob
   verificação. Apagar `SWEEP_BATCH_SIZE` de `src/jobs/contracts/sweeper/
   config.ts` mantinha o verde, que é precisamente a variável-fantasma que a
   rule `jobs-and-workers.md` promete acusar. O manifesto sai da varredura.

   Provado no caminho invertido: com `GHOST_VAR_PROVA_INVERTIDA` declarada, o
   gate reprova nomeando a variável; sem ela, 16/16 verde.

2. Duas varreduras perguntavam ao DISCO onde a `.claude/rules/testing.md`
   exige o git — "gate cuja resposta depende de onde roda não verifica nada".
   `envReaders()` e a varredura de órfãos usavam `walkFiles`, ao lado de um
   `trackedFiles()` que já perguntava certo. Um `.ts` não versionado deixava o
   gate vermelho local e verde no CI. Ambas passam a usar `trackedTs()`.

3. `w.command[1]` fixava a posição do entrypoint, embora `command` seja
   `readonly string[]` e só o elemento 0 seja contratado. Declarar
   `['node', '--max-old-space-size=512', 'x/run.ts']` fazia o gate acusar a
   FLAG como entrypoint não versionado e a varredura de órfãos reportar o
   arquivo real como "entrypoint que ninguém declara" — duas mensagens
   apontando para longe da causa. Agora o entrypoint é derivado (`entrypointOf`)
   e um teste novo cobra o contrato: `node` em [0] e exatamente um `.ts`.

4. `dependsOn` não detectava ciclo. Nome inexistente e auto-referência já eram
   barrados; um ciclo de dois saltos passava, descrevendo topologia que nunca
   inicia — e a suíte se chama "a cadeia de precedência se sustenta". Teste
   novo com busca em profundidade e o caminho do ciclo na mensagem.

Gate verde nos quatro: typecheck, format:check, lint e 12.011 testes com zero
falhas (os dois testes novos inclusos).

Assisted-by: Claude-Code:claude-opus-5
feat(deploy): manifesto declara as unidades executáveis e o que exigem
…fatos deixam de proibir o que já acontece

Quatro artefatos afirmavam "não há ambiente de homologação para remessa de pagamento" e "nunca
transmita para 'testar'". Era verdade em 10/08/2026, quando o ADR-0061 o registrou. Deixou de ser
em 06/09/2026, e o texto sobreviveu CINCO SEMANAS ao primeiro pagamento real.

O CUSTO NÃO FOI DE DOCUMENTAÇÃO

A rule entra em contexto por `path_glob_match` em toda sessão que toca o módulo, e o agente homônimo
é invocado proativamente para todo trabalho de CNAB: os dois têm peso de ordem, não de achado. Com o
texto vencido, um agente recusou executar ou planejar transmissão e argumentou contra a P.O. citando
o repositório — duas vezes, em 06/09 e em 14/10/2026. É o modo de falha que o handbook já produziu
("onze artefatos afirmando um layout que o banco recusa"), agora invertido: em vez de autorizar o
errado, proíbe o certo.

O QUE ENTRA

- ADR-0075, que supersede do ADR-0061 o §"Estado da integração" (`:23-27`) e a pendência 2 (`:137`).
  Todo o contrato do bucket — cinco prefixos, `status/` como única janela, caixa por convênio,
  idempotência do agente, credencial por role — permanece vigente e é premissa dele.
- A frase corrigida nos QUATRO artefatos que decidem sobre transmissão: `rules/cnab.md`,
  `agents/cnab240-bradesco.md`, `remittance-inspector.ts` e `bradesco-multipag-translator.ts`.
  A proibição NÃO cai: muda de razão. Gravar objeto no prefixo de saída É enfileirar pagamento
  (ADR-0060), e isso não depende de existir homologação. Quem transmite é o cliente, sobre título
  real, por decisão da P.O.
- `tests/cleanup/van-transmission-state-current.test.ts` — verifica a AUSÊNCIA das três frases nos
  quatro alvos, case-insensitive, resolvendo os caminhos por `git ls-files`. Verificado por inversão
  contra arquivo real: reintroduzindo a frase no agente, o gate acusa o arquivo e as duas claims.
- O ponteiro `Superseded by` no 0061, que também não registrava o ADR-0065 — escrever a linha pela
  metade seria introduzir o defeito, não deixar de consertá-lo.

O ESTADO REGISTRADO, COM FONTE E COM AS DUAS DATAS

TED, crédito em conta e boleto em produção desde 06/09/2026 (o piloto). Pix depois disso, com a
forma arbitrada por laudo escrito do banco em 05/09 e a operação confirmada pela P.O. em 14/10. O
Pix foi destravado por HABILITAÇÃO DO BANCO, não por mudança nossa — os PRs #981/#982/#1004 são
consequência do laudo e em boa parte removem exigência. Falta exercitar as demais contas-cedente.

ACHADOS DA REVISÃO, TRATADOS ANTES DO PUSH

A revisão do primeiro rascunho achou nove defeitos reais, e dois são de método:

1. O gate varria SÓ a rule — o arquivo consertado — e ficava verde com os outros três artefatos
   ainda negando a transmissão. Régua certa não se propaga sozinha: o grep que fecha a questão é o
   que volta vazio sobre o acervo inteiro.
2. Inserir 3 linhas no cabeçalho do 0061 deslocou o corpo e invalidou as próprias citações de linha
   que este diff introduz, em cinco lugares. O precedente foi conferido: `0060:41-46` aponta
   exatamente para a tabela de prefixos, e `0060:78-79` para as duas cláusulas — o repositório
   mantém essa disciplina.
3. A cláusula "a transição para `Transmitted` passa a depender de sinal externo" é do ADR-0060
   `:78-79`, não do 0061 `:117`. Estava atribuída ao documento errado num commit cujo propósito é
   higiene de citação.
4. A rule datava as quatro formas em 06/09, achatando a entrada posterior do Pix — o mesmo defeito
   de disciplina de data que este ADR existe para corrigir.
5. O gate exigia a PRESENÇA do identificador do ADR e da data no `.md`, que é o anti-padrão de
   `gate-asserts-property-not-prose.test.ts` (mede o vestígio, erra nas duas direções) — escapava
   do detector dele apenas por usar `assert.ok(includes(…))` em vez de `assert.match`. Removido:
   só a verificação de ausência fica.
6. Os literais eram case-sensitive e não casariam a cópia real, que é capitalizada em início de
   período em todo lugar onde a frase vive.
7. A contraprova afirmava `caught.length === 2`, deixando um detector inexercitado e punindo quem
   a fortalecesse. Virou propriedade: cada claim confronta o próprio exemplar literal.
8. O §4 dizia "única frente de validação ainda aberta" contradizendo o §"em aberto" do mesmo
   documento, que lista a confirmação pendente do banco (#863).
9. A norma do §5 — toda afirmação de estado operacional com data e fonte — NÃO é gateada, e isso
   passa a estar declarado no ADR, com o molde reutilizável nomeado (`inventory-counts.test.ts`).

Gate: 12014 testes, 0 falhas · typecheck, format:check, lint e docs:links verdes (`unaddressed` 0).

Refs #993

Assisted-by: Claude-Code:claude-opus-5
…ado da #1052 com os achados da revisão corrigidos

Conteúdo da #1052 (08/10), trazido para branch em cima da `dev` de hoje e corrigido aqui, em vez de
empurrar commits de correção na branch dela: a `dev` não recebe, nem no histórico, um commit que
afirma coisa falsa. Drift conferido antes do traslado — o merge-base da #1052 com a `dev` é
`d58a96f1`, o próprio HEAD, logo nenhum dos paths mudou desde a base dela.

O QUE VEM DA #1052

- `README.md` §Status (2026-06-23) apagada: ser datada ERA o defeito. As três afirmações falsas dela
  medidas uma a uma (Bruno removido em 06/10; `fin_outbox` existe nas migrations 0018/0028; ADR-0048
  é Accepted desde 05/08).
- `.claude/agents/bruno-api-client-expert.md` apagado — agente ATIVO no harness descrevendo os 266
  `.bru` que saíram com o `@usebruno/cli` (ADR-0074).
- Âncora morta do CLAUDE.md, o `overmind` invisível, o ponteiro para arquivo gitignored, a topologia
  velha de workers e o `Procfile` como terceira declaração do que roda.
- A afirmação "o compose gera os taskdefs" REMOVIDA, não invertida, em 3 artefatos.

OS NOVE ACHADOS DA REVISÃO, CORRIGIDOS

Dois eram bloqueantes:

1. A receita documentada NÃO FUNCIONAVA. `buildVanStorage` faz `throw` sem `VAN_S3_*`
   (`composition.ts:782`) e é chamado de forma eager na composição do `financial`
   (`:949`) — então o `http` também as exige, não só o grupo `van`. O contorno que a #1052
   sugeria (`OVERMIND_PROCESSES=http,worker-outbox`) e o `pnpm run serve` falham igual. O README
   passa a dizer isso, com o arquivo:linha, e o contorno falso sai.
2. A afirmação removida de 3 artefatos CONTINUAVA VIVA no `compose.yaml` (`:298`, `:355`) — o
   arquivo de que a afirmação fala. A varredura seguiu a lista de 3 artefatos da inquiry-0038 §4 em
   vez do assunto; o `grep -i taskdef` que a própria #1052 cita devolvia essas duas linhas.

Os outros sete:

3. O ADR-0068:28 registra que as VARIÁVEIS são postas à mão na console, e que os ambientes são
   operados por terceiro e "provisionados por taskdef". Quem ESCREVE o taskdef não está dito ali —
   os 4 artefatos novos afirmavam além da fonte, no PR cuja tese é não fazer isso.
4. A rule dizia que a topologia de 4 grupos vale "nos taskdefs", incluindo `van` — mas
   `workloads.ts:366` declara `provisioning: 'declared-only'` e a inquiry-0038 mediu no CloudWatch
   que `worker-van` não existe em ambiente nenhum. Declarado não é provisionado.
5. `README.md` se contradizia em 40 linhas: os `worker:*` listados sob "Servidor + processos de
   background" e, adiante, declarados inadequados para isso. Corrigido no bloco, não depois dele.
6. A receita manual omitia a cadeia `migrate` → `sync-permissions` → `http` que o manifesto declara
   (`workloads.ts:211`) e que o teste editado ao lado defende: subir a borda sem semear o catálogo
   devolve 403 mudo (#462).
7. Três citações `arquivo:linha` erradas no docblock do teste (`server.ts:124` → `:91`;
   `server.ts:268` → `:292`, em 3 lugares).
8. Três referências órfãs a `supplier-projection` como nome de processo no runbook 09 — desde a #407
   o processo é `worker-projections`, e a projeção roda dentro dele.
9. A inquiry-0038 §4 seguia declarando a divergência como não resolvida, com citações apontando para
   o texto oposto. Marcada como resolvida, com o `open_output` do gerador PRESERVADO em aberto — é
   por isso que a afirmação foi removida e não invertida.

Além disso, os profiles do compose: `docker compose up` sem `--profile` sobe só a infra, porque todo
serviço de aplicação está atrás de um. A lista nomeava 3 dos 5.

NÃO VERIFICADO

- `pnpm run dev` continua não executado: o `overmind` não está instalado nesta máquina.
- `.env.example` está em diretório negado pelas permissões desta sessão. Pela medição acima ele
  precisa de `VAN_S3_REGION` e `VAN_S3_BUCKET`, ou `cp .env.example .env && pnpm run dev` não sobe
  nem o `http`. Fica como pendência explícita.
- Comportamento do overmind (PORT por processo, `OVERMIND_CAN_DIE`) documentado sem fixar números,
  porque não pude exercer a ferramenta.

Gate: 12014 testes, 0 falhas · typecheck, format:check, lint e docs:links verdes (`unaddressed` 0) ·
`docker compose config` exit 0 (as mudanças no compose são só de comentário).

Refs #1052

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

chore(doc): o registro deixa de mentir — o que a VAN faz e como o repositório roda
…ADOS do manifesto

O `.env.example` era escrito à mão, não declarava `VAN_S3_REGION` nem `VAN_S3_BUCKET`, e
`cp .env.example .env && pnpm run dev` não subia nem o processo `http` — `buildVanStorage` faz
`throw` (`composition.ts:782`) e é chamado de forma eager na composição do `financial` (`:949`),
então a exigência nunca foi só do grupo `van`. Nenhum gate olhava o template.

A CAUSA, MEDIDA: O GATE DO MANIFESTO É DE MÃO ÚNICA

`workloads-manifest.test.ts` cobra manifesto → código ("toda variável declarada aparece no fonte")
e restringe ONDE se lê ambiente. **Nada cobrava a volta.** Uma env nova, lida num `*-config.ts`
legítimo e não declarada, passava verde. Custo de adoção da volta, medido antes de escrever: 5
variáveis — baixo o bastante para fechar agora.

O QUE ENTRA

- `src/deploy/env-catalog.ts` — 106 variáveis, cada uma com `description` (o que faz), `readBy`
  (onde entra) e `example` (valor de dev). Indexado por VARIÁVEL, não por unidade: `LOG_LEVEL` e
  `CORS_ORIGINS` estão em todas as treze unidades, e descrever treze vezes seria a duplicação que
  o anti-padrão 8 do CLAUDE.md proíbe.
- `scripts/deploy/generate-env-artifacts.ts` + `pnpm run env:generate` / `env:check` — escreve o
  `.env.example` e `handbook/infrastructure/14-environment-variables.md`.
- `tests/cleanup/env-catalog.test.ts` — a bijeção nas duas direções, incluindo **a volta que
  faltava**: toda env lida em `src/` tem entrada no catálogo.
- `tests/cleanup/env-artifacts-generated.test.ts` — o disco é IDÊNTICO ao gerado, na forma do
  `format:check` contra o Prettier.

GERAR, E NÃO CONFERIR — a decisão é do ADR-0073

Ele mediu a diferença: o gate da versão do Node cobrava só o major, e a divergência de minor
(`24.16.0` contra `24.15`) sobreviveu **verde**. Igualdade byte a byte não tem essa fenda, e a
mensagem de erro é acionável porque o conserto é um comando.

CINCO VARIÁVEIS LIDAS QUE NENHUMA UNIDADE DECLARA — E NÃO SÃO PARA DECLARAR

O gate da volta revelou código que lê env fora do caminho de boot. Declará-las no manifesto
afirmaria que uma unidade as exige, o que é falso; então o catálogo ganhou o campo `notComposed`,
que registra o motivo e as mantém fora do template:

- `BUDGET_PLANS_SEED_JSON` — `parseE2eBudgetPlansSeed` ficou órfã quando o ramo `memory` saiu do
  `server.ts`. O próprio arquivo registra isso em `server.ts:65`, e o `public-api` ainda a exporta.
- `MAGALU_*` (4) — `public-api/index.ts:36` do `contracts` ainda anuncia `magaluCloudConfig` como
  trabalho futuro, com o export já feito na linha 58. Nenhum composition root o instancia.

UM DEFEITO MEU, QUE O GATE NÃO PEGOU

A primeira versão do gerador classificava por três listas e ignorava as `constraints` — então
`EMAIL_FROM`, `EMAIL_FROM_RESET` e `EMAIL_FROM_INVITE`, que vivem só no `at-least-one` do
`worker-email`, DESAPARECERAM do template. Justamente as que decidem se a spec de e-mail monta.

O gate de bijeção não acusou porque a varredura dele conta as constraints e a do gerador não
contava: **duas definições de "declarada" no mesmo trabalho**, que é o defeito que ele existe para
impedir. Consertado com a categoria `constraintOnly` e, sobretudo, com um caso novo que mede o
ARTEFATO em vez da definição — toda variável catalogada chega ao template, ou tem `notComposed`.
Medir o efeito é imune a divergência de critério.

Conferido depois: zero variáveis perdidas na substituição do template. As cinco de produção saem
comentadas, por desenho.

O claim `verify:` da rule `jobs-and-workers.md` sobre `WORKER_GROUP` foi atualizado: o seletor
agora é lido no runner, declarado no manifesto e DESCRITO no catálogo — três lugares, cada um com
um papel.

NÃO VERIFICADO

`pnpm run dev` continua não executado: o `overmind` não está instalado nesta máquina.

Gate: 12024 testes, 0 falhas · typecheck, format:check, lint e docs:links verdes (`unaddressed` 0).

Refs #1059

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

Achado no primeiro boot real do `.env.example` gerado: `worker-email` saiu com 78 e, sob o
overmind, levou os outros quatro processos.

    worker-email | email-dispatch: config de e-mail inválida (invalid-provider-config)
    worker-email | Exited with code 78
    http         | Interrupting...

A causa imediata era um `example` errado meu: `SMTP_USER` e `SMTP_PASS` saíam VAZIOS no template,
com a descrição afirmando "vazio em servidor local sem autenticação". O código afirma o oposto —
`REQUIRED_FIELDS` em `nodemailer-config.ts:40-46` recusa `undefined` E string vazia.

A CAUSA ESTRUTURAL ERA OUTRA, E É O QUE ESTE COMMIT CONSERTA

`worker-email` declarava as oito `SMTP_*` e a `RESEND_API_KEY` em `envWithDefault` — categoria que
significa "tem substituto no código; ausência não derruba". Sob `EMAIL_PROVIDER=smtp` isso é falso
para cinco delas. O manifesto mentia, e o `.env.example` herdou a mentira.

A SAÍDA QUE EU IA TOMAR, E POR QUE ELA ERA PIOR

Eu propus uma constraint nova, `required-when EMAIL_PROVIDER=smtp`. O dono recusou e propôs pensar
em nível de ADAPTER: a porta é satisfeita por um adapter, e o que esse adapter precisa do ambiente
é assunto dele. Está certo, por três razões:

1. `required-when` acoplaria o manifesto ao VALOR string de outra variável — renomear o provider
   quebraria a declaração sem nada acusar. Exatamente a classe de afirmação frágil que os outros
   commits desta série passaram o tempo consertando.
2. O conceito JÁ EXISTE no manifesto: `Capability` declara o TIPO de serviço, nunca o fornecedor, e
   `{ kind: 'smtp' }` está lá desde sempre. Eu não o usei.
3. Adapter novo (SES, por exemplo) não toca o manifesto central: a capacidade já está declarada.

O QUE ENTRA

- `src/deploy/capability-adapters.ts` — quatro adapters, cada um com o ambiente que ELE exige, como
  é selecionado (`selectedBy`) e onde isso é validado (`validatedBy`): `nodemailer-smtp`,
  `resend-http`, `email-in-memory` e `magalu-object-storage`.
- `worker-email` perde as nove envs de transporte. A omissão é comentada no manifesto, para não se
  ler como esquecimento.
- O gerador ganha seção por capacidade, com os ramos visíveis — "ESCOLHA UM ADAPTER" —, o bloco do
  adapter default ativo e os demais comentados. Um template com dois blocos ativos da mesma
  capacidade descreveria configuração impossível.
- Três gates novos: variável não pode ser declarada por unidade E por adapter; todo adapter atende
  capacidade que alguma unidade requer; adapter que exige ambiente nomeia quem o valida.

E ISSO UNIFICOU UM PROBLEMA QUE EU TRATAVA SEPARADO

As quatro `MAGALU_*` — que o gate da volta acusou no commit anterior e para as quais eu inventei o
campo `notComposed` no catálogo — são envs de adapter, não envs órfãs. O Magalu é um dos três
provedores que o docblock de `s3-compat` nomeia. Agora ele é um adapter declarado, com o
`notComposed` no ADAPTER (a peça inteira à espera de ligação), e o campo do catálogo volta a
significar só o que deve: resíduo de código que perdeu o chamador — hoje, apenas
`BUDGET_PLANS_SEED_JSON`.

A ASSIMETRIA COM AS `S3_*` É DELIBERADA

Elas continuam declaradas na unidade porque a capacidade `s3-compat` tem adapter FIXO composto (o
SDK da AWS, que serve S3 e MinIO mudando `forcePathStyle` — ADR-0019): não há escolha a fazer em
runtime, então a exigência é mesmo da unidade. `selectedBy` ausente significa exatamente isso.

Gate: 12026 testes, 0 falhas · typecheck, format:check, lint e docs:links verdes.

Refs #1059

Assisted-by: Claude-Code:claude-opus-5
… ser afirmada errado

Três consertos que só o boot real podia revelar — o `.env.example` gerado subiu a stack inteira em
14/10/2026, e o caminho até lá custou dois ciclos de diagnóstico que não deveriam ter existido.

1. O LOG DO WORKER ENGOLIA O CAMPO

`specs.ts` formatava o erro como `config de e-mail inválida (${config.error.tag})` — e o `tag` é
`invalid-provider-config`, que não diz QUAL variável falta. O `reason`, que carrega o `field`, era
descartado. Sob o overmind o estrago cresce: o `worker-email` sai com 78 e leva os outros quatro,
então o terminal enche de `Interrupting...` sem nenhuma pista.

Dois boots seguidos ficaram sem diagnóstico, e a causa só apareceu com uma sonda que chamava
`parseEmailConfig` à parte — ou seja, o processo JÁ SABIA o que eu precisava saber.

O ADR-0068 é explícito sobre o que justifica o fail-fast: "o processo não sobe E DIZ QUAL CAMPO
FALTA — é o único [modo de falha] que chega a quem tem a mão no problema". Sem o nome do campo, só
a primeira metade era cumprida.

Entra `describeEmailConfigError`, no molde de `describeVanS3ConfigError`, exportada pelo
`public-api` do `notifications`. Antes e depois, medidos:

    antes:  config de e-mail inválida (invalid-provider-config)
    depois: config de e-mail inválida — provider "smtp" selecionado e sua config nao resolve:
            SMTP_USER ausente ou VAZIA (string vazia conta como ausente)

Segurança, pela régua do `describeVanS3ConfigError`: das variantes, só `invalid-port` e
`invalid-max-connections` carregam `raw`, e nenhuma vem de credencial — `SMTP_USER`/`SMTP_PASS`
falham como `missing-env`, que traz apenas o NOME (CWE-532). O `raw` ecoado passa por
`echoEnvValue`, contra forja de linha no stderr (CWE-117). Seis casos cobrem isso, inclusive os
dois de segurança.

2. EU AFIRMEI A PORTA ERRADA, EM TRÊS LUGARES

README, catálogo e script diziam "o overmind define PORT por processo, então a porta da borda NÃO é
a 3000". O boot real: `Server listening at http://127.0.0.1:3000`, e `GET /health` → 200.

A causa do erro é minha: tomei por conclusão o que foi medido no fonte do overmind ISOLADAMENTE,
sem considerar que o `.env` participa — e o `.env` tem precedência. O texto corrigido afirma só o
que foi exercido (com `PORT` declarada, é ela que vale) e marca o outro caminho como não exercido.

É a classe de defeito que este PR existe para eliminar, cometida dentro dele.

3. O SCRIPT DE SETUP CONFIAVA NA MENSAGEM DO SEED

`seed:admin` é idempotente e NÃO sobrescreve: se o e-mail existe, encerra com sucesso sem tocar a
senha. Em produção é a garantia certa — impede trocar a senha do administrador real por engano. Em
desenvolvimento produz 401 silencioso: o script dizia "pronto" e a senha impressa não valia.

Foi o que aconteceu: `admin@local.test` existia desde 06/10, de outra sessão, com senha que ninguém
registrou. O script agora pergunta ao banco ANTES e, se o e-mail estiver tomado, usa um livre.

A assimetria vale registrar: o passo que PROVA o par ES256 foi desenhado medindo o efeito na função
de boot, e pegou o problema na hora; o passo do admin confiava no relato, e o problema apareceu dois
ciclos depois.

PROVADO DE PONTA A PONTA

`infra up` → 7 módulos migrados → 47 permissões → admin → `overmind start` com os cinco processos,
`email-dispatch` incluído → `GET /health` 200 → `POST /api/v2/auth/login` 200 com accessToken
(238 chars) e refreshToken → `GET /api/v1/me` 200.

Gate: 12032 testes, 0 falhas · typecheck, format:check, lint e docs:links verdes.

Refs #1059

Assisted-by: Claude-Code:claude-opus-5
…ete armadilhas medidas

Todo o ferramental construído para provar o `.env.example` gerado vivia em `.tmp/`, que é
gitignored — some ao limpar o diretório ou remover a worktree. O conhecimento mais caro do trabalho
estava no lugar mais frágil.

O QUE ENTRA

- `handbook/runbooks/local-stack-setup.md` §9 — as sete armadilhas do ciclo, medidas, cada uma com
  o custo em ciclos de diagnóstico. O runbook já existia (349 linhas) e é de antes do overmind.
- `scripts/debug/` — o ferramental, com `macos/` para o que depende de SO.

AS TRÊS QUE MAIS CUSTARAM, E NENHUMA PRODUZ ERRO NO BOOT

1. **Precedência de env tem três camadas.** `--env-file` do Node NÃO sobrescreve env de processo, e
   o overmind injeta o `.env` como env de processo. Como o template declara `AUTH_SEED_JSON=""`, o
   seed real nunca vencia: `parseE2eAuthSeed` lia vazio, devolvia `undefined` — o caminho CERTO de
   produção — e o sintoma aparecia 8 requests depois, como 401. Trocar para `source` resolveu a
   precedência e criou outro: o shell comeu as aspas duplas do JSON, 2255 chars → 1999, exatamente
   as 256 aspas. A regra: o mecanismo define a sintaxe E a precedência, e os dois têm de casar.
2. **`--root` do overmind** usa o diretório do Procfile como raiz. Um defeito, três sintomas que
   pareciam independentes: MODULE_NOT_FOUND, `tee` sem destino e a VERSÃO DO NODE mudando (o
   `.nvmrc` ficou fora de alcance). O sinal estava na primeira linha: `Tmux session ID: -tmp`.
3. **São dois rate limits independentes** — login (5/min) e geral (100/janela). Resolvido o de
   login, a suíte disparou 185 requests em 1s e 142 de 347 voltaram 429 no geral.

POR QUE `scripts/debug/<so>/`

Ferramental de debug local pode depender do SO; o resto do repositório não pode. O orquestrador é
Swift — roda sem toolchain extra num Mac e não roda nos runners Linux. Deixá-lo na raiz de
`scripts/debug/` daria a entender que é portável. Um `linux/` nasce quando alguém precisar, sem
mover o que já está lá.

Separado de `scripts/e2e/` por natureza, e foi um GATE que apontou a diferença:
`e2e-scripts-non-destructive.test.ts` exige que todo `.sh` de lá carregue o helper único — convenção
de smoke test, que um wrapper de boot não satisfaz nem deveria. O escopo era maior do que eu achava.

O ESTADO MEDIDO DA SUÍTE, QUE O FERRAMENTAL REPRODUZ

`swift scripts/debug/macos/local-e2e.swift --reset` → 68 → 152 (seed E2E) → **191 de 241 requests**,
6 arquivos verdes, **zero erro 500** e **zero defeito de aplicação**. Dos 50 que faltam: 48 são
limite do Hurl (`[Captures]` não cruzam arquivos), 24 asserts de 403 são o RBAC em bypass — a #634
—, 1 supõe resíduo de banco e 2 perderam a condicionalidade na conversão do `.bru`.

⚠️ A suíte não pode chegar a 100% enquanto o RBAC estiver em bypass, e isso dá à #634 um argumento
novo: ela deixa de ser só segurança e passa a ser pré-requisito para a suíte poder validar
autorização. Hoje 24 casos de segurança ficam permanentemente vermelhos, e quem olha não distingue
"RBAC desligado" de "autorização quebrada".

Gate: 12032 testes, 0 falhas · typecheck, format:check, lint e docs:links verdes · ciclo completo
reexecutado nos caminhos novos (8/8 perfis, 191 requests).

Refs #1059

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

feat(deploy): o template de env e a doc de variáveis passam a ser GERADOS do manifesto
…ixa de afirmar 82/241

O conhecimento de decisão desta frente vivia no handbook — que é ACERVO, lido sob demanda. As rules
de `.claude/rules/` carregam por `path_glob_match` e têm peso de ORDEM: é lá que precisa estar o que
um agente tem de saber antes de editar.

DUAS LACUNAS MEDIDAS

1. **Nada cobria `src/deploy/`.** O manifesto, o catálogo de env e os adapters de capacidade não
   tinham rule alguma — quem os editasse não recebia nada. Entra `deploy-manifest.md`, com paths
   para `src/deploy/**`, `scripts/deploy/**` e `scripts/debug/**`.
2. **`api-collections.md` estava DEFASADA**, afirmando "medido em 06/10, 82 de 241 requests
   executam". Hoje são 191 — o salto veio do seed E2E e do banco limpo, não de mudança na suíte. É
   o mesmo defeito que o ADR-0075 corrigiu na rule do CNAB, no mesmo diretório: afirmação de estado
   que envelheceu e continua carregando automaticamente.

O QUE A RULE NOVA REGISTRA, e cada item custou pelo menos um ciclo

- Os artefatos de env são **gerados**; editá-los à mão é desfeito e o gate acusa.
- **Três lugares possíveis** para uma variável, e a escolha diz o que ela é: lista da unidade,
  adapter de capacidade, ou `notComposed`.
- ⚠️ **Não criar constraint condicional de valor** (`required-when X=y`): acopla o manifesto ao valor
  string de outra variável. O vínculo correto é estrutural, pelo adapter.
- 🚨 **Precedência de env tem três camadas**, com a prova em uma linha de shell. `--env-file` não
  vence env de processo; `source` vence mas exige quoting — trocar um pelo outro sem requotar fez o
  shell comer 256 aspas de um JSON, que chegou presente e corrompido.
- **Variável comparada como string literal não aceita sinônimo:** `CORE_API_E2E` só passa com `'1'`.
- **Dois rate limits independentes**, e subir um não afeta o outro.
- **`example` vai para arquivo público** — nunca credencial nem dado de cadastro.
- **Mensagem de erro de config NOMEIA o campo**, com o molde e as duas CWEs que a limitam.

E a `api-collections.md` passa a registrar que **a suíte não pode passar enquanto o RBAC estiver em
bypass**: os 24 asserts de 403 são irreconciliáveis com "todo autenticado é super-usuário", e o modo
está fixado por código. A instrução é explícita — não consertá-los na suíte, não tratá-los como
defeito.

O `llms.txt` vai de "17 rules" para 18. Quem apontou foi o `inventory-counts.test.ts`, no primeiro
`pnpm test` — contagem cravada em doc é exatamente o que ele caça.

Refs #1059

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

docs(harness): a rule que faltava para `src/deploy/`, e a da suíte deixa de afirmar 82/241
Copilot AI balanced review requested due to automatic review settings October 15, 2026 04:07

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.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@claude

claude Bot commented Oct 15, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

GabrielAderaldo and others added 2 commits October 15, 2026 02:30
… default

Implementa o conceito "issue fecha entrando na `dev`; a release é o ajuntamento
que fecha na `main`". A issue passa a responder uma pergunta só — o código existe
e está integrado — e quem responde "está no ar" vira a release.

POR QUE UM WORKFLOW E NÃO A TROCA DA BRANCH DEFAULT

O GitHub fecha issue automaticamente só quando o PR entra na branch default, e a
default deste repositório é a `main`. Trocá-la para `dev` daria o fechamento de
graça, mas moveria o Dependabot e o code scanning para a linha de trabalho: o
alerta "N vulnerabilidades high na branch default" passaria a falar da `dev`, e
não do que está no ar — e foi esse alerta que fundamentou a promoção do #1031.

O PASSIVO MEDIDO, QUE É PEQUENO

Em 15/10/2026, cruzando as 153 issues abertas com os 120 PRs mergeados na `dev`:
30 eram citadas e exatamente UMA declarava fechamento e seguia aberta — entregue
em 09/09, publicada na `v1.0.0-rc.3`, aberta por 51 dias. Fechamento manual não
falha em massa; falha em silêncio, uma issue por vez.

A PROPRIEDADE É "NENHUM RESULTADO SILENCIOSO", E ELA TEM DOIS LADOS

Fechar a issue errada e deixar de fechar a certa são falhas de igual gravidade, e
as duas são invisíveis quando o job termina verde. A revisão `/code-review high`
achou seis defeitos nesta implementação, todos dessa família, cinco deles medidos
por execução real. Todos corrigidos aqui, com teste por caso:

  1. (HIGH) o `catch` único convertia QUALQUER falha de consulta em "issue não
     existe" e seguia verde — rate limit, 5xx e token sem escopo fariam o job
     declarar que não havia nada a fechar. Agora 404 segue, e transporte, permissão
     ou rate limit ficam VERMELHOS (`isNotFound`).
  2. `gh issue view <N>` NÃO distingue issue de PR: medido com gh 2.95.0, o #1061
     (um PR) devolve `MERGED` com exit 0. Um PR aberto devolveria `OPEN` e o script
     chamaria `gh issue close` sobre um pull request — com `issues: write` apenas,
     ou fecharia um PR vivo ou deixaria o job vermelho depois do merge. A distinção
     confiável é a chave `pull_request` de `repos/{repo}/issues/{n}`.
  3. a janela de negação de 40 caracteres produzia falso NEGATIVO em frase comum:
     "Não houve regressão: fecha a #12" e "Mudança sem risco, fecha a #12" não
     fechavam nada, e a referência nem entrava no log. A negação passa a ser exigida
     ADJACENTE ao verbo; "sem" sai da lista.
  4. um verbo governa a LISTA inteira: "Fecha as #816 e #820" fechava só a #816, o
     log imprimia um fechamento e o autor concluía que as duas entraram.
  5. citação não é declaração: bloco cercado, código inline e blockquote são
     removidos antes da extração — PR de consolidação transcreve o corpo do PR que
     substitui, e PR de release transcreve CHANGELOG.
  6. `base.sha` pode ser anterior ao próprio script (PR mergeado enquanto BEHIND),
     e `node` morreria com ERR_MODULE_NOT_FOUND num job sem defeito nenhum. O passo
     checa a existência do arquivo antes de invocá-lo.

SEGURANÇA DO `pull_request_target`

É `_target` e não `pull_request` porque o repositório é público e pode receber PR
de fork, cujo token é read-only: `gh issue close` falharia no caso que mais precisa
de rastro. O que torna `_target` perigoso é executar CÓDIGO do PR com o token do
base — aqui o checkout é fixado em `base.sha`, nenhum passo instala dependência ou
roda build, e título e corpo vão por env, nunca interpolados no `run`.

Gate: typecheck, format:check e lint verdes; 12063 testes, 0 falhas, 20 ignorados.
Os 26 casos novos cobrem a extração (com texto real de PR mergeado), a classificação
de PR vs issue, a distinção 404 vs transporte e a estrutura do workflow.

Assisted-by: Claude-Code:claude-opus-5
…r-na-dev

feat(ci): issue fecha ao entrar na dev, e deixa de depender da branch default
@GabrielAderaldo GabrielAderaldo self-assigned this Oct 15, 2026
@GabrielAderaldo GabrielAderaldo changed the title chore(release): integra a dev na main — o manifesto de workloads e o env gerado a partir dele (#1051, #1060) chore(release): integra a dev na main — o manifesto de workloads, o env gerado dele e o fechamento de issue na dev (#1051, #1060, #1064) Oct 15, 2026
@GabrielAderaldo
GabrielAderaldo merged commit eec6492 into main Oct 15, 2026
22 checks passed
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.

2 participants