Repository navigation
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
Conversation
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
There was a problem hiding this comment.
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.
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
9 tasks
… 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promove a
devpara amain— que é produção neste repositório. 6 unidades de entrega desde o#1046, nenhuma delas de release: a
mainjá declarava
1.0.0-rc.4desde o #1031, e a tagv1.0.0-rc.4foi criada em15/10 sobre o commit que a promoveu (
e6bc7e30), fechando o passo 4 do rito do ADR-0066 — que haviaficado pendente por nove dias, com a
maindeclarando uma versão sem tag nem releasecorrespondente.
O que vai ao ar
Adicionado
cobra (#1051)
.env.examplee a doc de variáveis passam a ser gerados do manifesto — o gatedeixa de ser de mão única (#1060, que fechou
a [deploy] o .env.example e a doc de env passam a ser DERIVADOS do manifesto — e o gate do manifesto é de mão única #1059)
dev, e deixa de depender da branch default(#1064) — ver a nota ao final
Corrigido
email-dispatchpassa a nomear a variável que falta, em vez dedevolver só o tag do erro — o operador recebia
exit 78sem pista, sob o overmind, com os outrosquatro processos caindo em cima da mensagem (feat(deploy): o template de env e a doc de variáveis passam a ser GERADOS do manifesto #1060, ADR-0068)
Alterado e interno
ADR-0075, a rule do CNAB, o agente, o inspetor e o tradutor
(#1058, sucessor consolidado de chore(doc): o repositório deixa de mentir sobre como ele roda #1052 e
docs(financial): o ADR-0075 registra a VAN em produção, e quatro artefatos deixam de proibir o que já acontece #1056, que fechou também a [financial] o piloto da VAN aconteceu e passou, e o repositório ainda diz que nunca se deve transmitir #993)
src/deploy/, e a da suíte deixa de afirmar 82 de 241requests — hoje são 191 (#1061)
Medições
main...devsrc/modules/tocado+3/−3e+5/−4, zero linha fora de comentário). Os dois denotificationssão aditivos: a funçãodescribeEmailConfigErrore o seu reexport napublic-api.src/workers/runner/specs.ts, +9/−1) — passa a chamar a função nova no bootdev..mainmainé ancestral dadevNota de convenção: a mensagem de boot vai SEM acentuação, de propósito
A função nova
describeEmailConfigErroremite 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:
src/shared/persistence/module-driver-config.ts:33src/modules/financial/adapters/van/van-s3-config.ts:24src/modules/auth/adapters/http/jwt-key-config.ts:16src/modules/contracts/adapters/storage/s3-config-aws.ts:67src/modules/programs/adapters/http/logo-storage-config.ts:22Não conflita com a tabela de idioma do
CLAUDE.md: ela exige acentuação completa emdocumentação, diálogo e commit, e apenas PT-BR em string ao humano.
O que de fato divergiu
src/workers/runner/specs.ts:283monta o prefixo com acento e concatena a mensagem sem:O prefixo acentuado é preexistente — a linha substituída já dizia
inválida. O que nasce nesterange é 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ãdescribeVanS3ConfigErrorapenas 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 nadev, e não mais ao chegar namain.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→mainaguarda aprovação da P.O. (decisão de 06/10/2026): ela deixou de serpasso automático da cadeia. Este PR fica aberto e verde até lá, como o #1031 ficou.
Assisted-by: Claude-Code:claude-opus-5