Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
38 changes: 14 additions & 24 deletions .claude/rules/api-collections.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
---
paths:
- 'api-collections/**'
- 'scripts/e2e/bruno-all.sh'
verify:
- claim: 'um único script executa a coleção — só ele chama `bru run`'
root: 'scripts'
pattern: 'bru run'
expect:
- 'scripts/e2e/bruno-all.sh'
- claim: 'a lista de pastas rodadas é literal e não inclui budget-plans'
root: 'scripts'
pattern: 'MAIN_FOLDERS=(0-auth 1-users 2-me 3-roles-permissions 4-auth-security 5-auth-improvements 6-contracts 7-partners 8-programs)'
expect:
- 'scripts/e2e/bruno-all.sh'
- claim: 'o rc de z-pending-fixes entra no exit code do gate'
root: 'scripts'
pattern: '[ $RC_MAIN -eq 0 ] && [ $RC_PENDING -eq 0 ]'
expect:
- 'scripts/e2e/bruno-all.sh'
- 'api-collections-hurl/**'
- 'tests/fixtures/http-edge/**'
---

Coleções `.bru` que exercitam a borda HTTP. Normativo: **[ADR-0038](../../handbook/architecture/adr/0038-bruno-cli-mandatory-and-bru-authoring.md)** — um `.bru` escrito e não executado é cobertura ilusória, e **o resultado do `bru run` é a fonte de verdade, nunca a leitura do arquivo**. Rodar via `pnpm run test:integration:all`, que sobe MySQL + MinIO e boota o servidor real. Para inspecionar o body de uma resposta: `E2E_JSON_REPORT=1 pnpm run test:integration:all` → `test-results/main.json`.
Suíte da borda HTTP em **Hurl** — normativo: **[ADR-0074](../../handbook/architecture/adr/0074-hurl-replaces-bruno-http-edge-suite.md)**. O resultado do runner é a fonte de verdade, nunca a leitura do arquivo.

- **O nome `z-pending-fixes` mente — a pasta BLOQUEIA o gate.** Ela nasceu expected-fail, como o ADR-0038 §2 manda isolar, mas os cinco tickets foram implementados e ela virou suíte de **regressão**: a linha final do runner é `[ $RC_MAIN -eq 0 ] && [ $RC_PENDING -eq 0 ]`, e o rc dela entra no exit code. ⚠️ **Não existe hoje pasta expected-fail de verdade** — pôr ali um `.bru` que reprova de propósito derruba o gate inteiro. Renomear foi avaliado e recusado em 2026-08-05: tocaria 17 registros históricos que o [ADR-0057](../../handbook/architecture/adr/0057-claude-md-as-canonical-agent-doc.md) §5 proíbe reescrever, mais errata num ADR imutável — custo maior que o do nome errado. A [Inquiry-0026](../../handbook/inquiries/0026-async-human-in-the-loop-and-drizzle-1-0.md) mede se o Bruno permanece.
- ⚠️ **`api-collections/` e os 266 `.bru` foram REMOVIDOS em 2026-10-06**, com o `@usebruno/cli` (−317 pacotes). O [ADR-0034](../../handbook/architecture/adr/0034-adopt-bruno-api-client-cli.md) e o [ADR-0038](../../handbook/architecture/adr/0038-bruno-cli-mandatory-and-bru-authoring.md) estão `Superseded` — não citar nenhum dos dois como norma. O que o 0038 estabeleceu e continua valendo está reafirmado no §5 do ADR-0074. Registro histórico (`handbook/specs/**`, `tickets/**`, `CHANGELOG`) **não** foi reescrito e ainda descreve o Bruno: era verdade quando foi escrito.

- **Existir na coleção não é ser executado: `budget-plans/` tem 14 `.bru` que nenhum runner roda.** As pastas rodadas são uma **lista literal** dentro do script (`MAIN_FOLDERS=(…)`), e `budget-plans` não está nela — nem em nenhum outro script, porque `bruno-all.sh` é o único lugar do repositório que chama `bru run`. São casos reais (403 de bare-user, not-found, payload malformado) parados em cobertura ilusória, exatamente o que a norma do ADR-0038 condena. **Criar `.bru` numa pasta nova não a coloca no gate** — sem editar a lista, o arquivo nasce morto.
- **A suíte convertida está `.gitignore`d, e isso não é descuido.** `api-collections-hurl/` (241 requests em 23 arquivos sob `casos/`) e `api-collections-http/` são uso interno enquanto o seed E2E não existir. O `.hurl` **nasce sem segredo** (variável vem de `--variables-file`), então é versionável no dia em que virar gate; o `.http` **não é** — ele só funciona com o token colado dentro do arquivo.

- **Um único `bru run` para toda a suíte, senão o token some.** O login por perfil acontece uma vez, em `0-auth/`, e viaja por `bru.setVar`; o encadeamento entre requisições é por `seq` + `setVar`. Rodar pasta a pasta abre processos distintos e **perde a variável** — é o bug do 401 que já custou investigação. Em E2E o rate-limit de login é afrouxado por `AUTH_LOGIN_RATE_LIMIT_MAX`.
- **Ainda não há gate E2E da borda, e isso é anterior à troca de ferramenta.** O `bruno-all.sh` caiu em 05/10 e nada o substituiu: medido em 06/10, **82 de 241** requests executam, o resto para em variável indefinida — 8 perfis de token que exigem o **seed E2E** (nenhum e-mail do environment existe no banco de dev) e IDs criados em outro arquivo. Não citar um `.hurl` daqui como prova de que uma rota funciona sem ter rodado.

- **Sintaxe e fidelidade ao schema real, não ao esperado.** O arquivo começa em `meta {` — **comentário `#` no topo faz o parser do Bruno rejeitar**; nota vai em `meta { docs }`, `folder.bru` ou `//` dentro de `script:*`. O body se escreve **depois de ler o schema Zod da rota**: `z.discriminatedUnion` exige o discriminador (`mode`, `kind`), e `min(1)` e nome exato de campo não perdoam. Dados precisam ser válidos de verdade — CPF/CNPJ por módulo 11 (`scripts/seed-partners.ts`) e UUID v4 real, porque o nil UUID `0000…0000` devolve **400, não 404**.
- **`[Captures]` não cruzam arquivos** (medido: capture em `a.hurl` + uso em `b.hurl`, mesmo comando, `--jobs 1` → `you must set the variable`). Então ou o arquivo é autocontido (login → ações), ou o token vem de `--variables-file`. **`--jobs 1` é obrigatório**: `--test` paraleliza por default e o rate limit de login (5/min) é compartilhado — dois arquivos logando se auto-envenenam com `429`. **`--continue-on-error`** em CI, porque o Hurl para no primeiro assert que falha dentro do arquivo.

- **Asserção tolerante ao código real, invariante forte.** Validação de querystring pode responder **400** (Zod) onde se esperaria 422 — aceitar o real (`expect([400, 422]).to.include(status)`) em vez de afrouxar a rota para caber no teste. O que **nunca** se aceita é **500**.
- **`hurlfmt --check` antes de commitar.** É o único gate de sintaxe que existe (não há LSP nem JSON schema), e pega o que passa batido na leitura: `{{process.env.X}}` — sintaxe do Bruno — é truncado para `{{process}}` **sem erro**, e body de texto sem a cerca ```` ``` ```` faz a primeira palavra do corpo ser lida como método HTTP.

- **Asserção tolerante ao código real, invariante forte.** Validação de querystring pode responder **400** (Zod) onde se esperaria 422 — aceitar o real em vez de afrouxar a rota para caber no teste. O que **nunca** se aceita é **500**; sem status conhecido, o request afirma `[Asserts] status < 500`.

- **Dado de cadastro é sintético, e CPF precisa ser único por criação.** `auth_user_cpf_idx` (migration 0010) recusa documento repetido: a coleção reusava **um** CPF em 11 requests de criação e só o primeiro passaria. CPF/CNPJ por módulo 11, UUID v4 real (o nil UUID devolve **400**, não 404), e `{{newUuid}}` para valor único por execução. **CPF inválido de propósito se preserva** — há teste que depende dele para exercitar o 422.

- **Fixture binário de upload vive em [`tests/fixtures/http-edge/`](../../tests/fixtures/http-edge/)**, versionado, não dentro das pastas de saída — que são gitignored e levariam o arquivo para fora do git. Os caminhos nos `.hurl`/`.http` são relativos ao arquivo e variam com a profundidade (`casos/users.hurl` vs `casos/regressao/distrato.hurl`).
2 changes: 1 addition & 1 deletion .claude/rules/supply-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ As quatro settings de quarentena e a concordância de versão do pnpm entre `pac

- **Dependência nova se justifica no PR, antes do merge** ([ADR-0011](../../handbook/architecture/adr/0011-supply-chain-hardening.md) §5): por que **esta** lib e não a alternativa — inclusive "implementar interno"; mantenedor com 2FA, atividade recente, bus factor; última publicação dentro de 6 meses. Antes disso, verificar se o **Node 24 já resolve**: `fetch` global, `crypto.randomUUID()`, `crypto.subtle`, `node --env-file`, `structuredClone`, `node --watch`, `node:test`. Cada dep removida é superfície de ataque a menos.

- **Ferramenta de teste nunca vira dependência de produção.** `@usebruno/cli` é `devDependency` pinada; o `src/` não a conhece. A exceção de supply-chain que ela exige é **cirúrgica, por versão**, no espírito do `undici-types@6.21.0` — não afrouxar a política global para acomodar uma ferramenta.
- **Ferramenta de teste nunca vira dependência de produção, e a melhor é a que não entra no manifesto.** O exemplo vivo desta regra era o `@usebruno/cli`: `devDependency` pinada, fora do `src/`, com exceção de supply-chain **cirúrgica por versão** (`semver@5.7.2 || 6.3.1`) no espírito do `undici-types@6.21.0`. Em 2026-10-06 ele saiu ([ADR-0074](../../handbook/architecture/adr/0074-hurl-replaces-bruno-http-edge-suite.md)) e **levou 317 pacotes** consigo — a suíte da borda passou a ser **Hurl**, um binário instalado por fora (brew), que não entra no `package.json` nem na árvore transitiva. As duas exceções que o Bruno exigia (`protobufjs`, `semver@6`) foram removidas depois de medir com `pnpm why` que nada mais as trazia. A lição que fica: ferramenta de desenvolvimento fora do manifesto tem superfície de ataque **zero** no projeto, e exceção de política sobrevive à dependência que a justificou se ninguém a varrer.

- **PBE (MagaluCloud) não recebe dado real** ([ADR-0021](../../handbook/architecture/adr/0021-aws-primary-magalu-pbe-supersedes-0007.md)). Ao editar config de deploy, `compose*.yaml` ou connection string de homologação: **sem** dump do legado, **sem** dado real ou pessoal, **sem** integração Bradesco real — só sandbox e dataset sintético. Produção e dado pessoal vivem na AWS (`sa-east-1`, residência BR). Esta restrição é de LGPD e **não tem enforcement mecânico**: depende de quem edita.

Expand Down
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,23 @@ test-results/
# Scripts de diagnóstico black-box descartáveis (ex.: login-diag) — uso local,
# nunca versionar; os outputs em out/ podem conter dados de prod (tokens redatados).
tools/bugs-scripts/

# Artefatos de QA/security testing da borda HTTP (.rest, harness Go, laudos de
# descoberta). Contêm Bearer token de sessão, enumeração de rotas internas e provas
# de vulnerabilidade — exatamente o que um atacante quer. Os repos são PÚBLICOS, então
# nenhum desses artefatos pode nascer rastreável. Uso local, contra o dev server.
penTest_rest/

# Coleções convertidas a partir de api-collections/ (.bru): `.hurl` para execução/CI e `.http`
# para disparar request pela IDE. Uso interno — ambas carregam o token da sessão quando
# preenchidas (`_vars.env` no caso do Hurl, `@token` no caso do .http), e os repos são PÚBLICOS.
# Quando o `.hurl` for promovido a gate de CI, a pasta `casos/` passa a ser versionada: ela
# nasce SEM segredo (variável vem de --variables-file) — só o `_vars.env` é que nunca entra.
api-collections-http/
api-collections-hurl/

# Cockpit (IDE) — estado e configuração de workspace da máquina de quem desenvolve:
# tasks.json (comandos do painel de Tasks), databases.json, layouts .ckp e worktrees.
# Não é configuração do projeto: os comandos dependem de caminhos e de arquivos locais
# (ex.: .tmp/local.env, também ignorado), então versionar só espalharia task que não roda.
.cockpit/
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Backend do ERP Bem Comum, modelado como **modular monolith**. Vários módulos d
>
> **Source of Truth:** [`handbook/`](./handbook/) (`handbook/architecture/adr/` vence tudo). Contexto canônico em [`CLAUDE.md`](./CLAUDE.md). Orquestrador, agentes e skills em [`./.claude/`](./.claude/).
>
> **Regras invariantes:** sempre `pnpm`, nunca `npm` (ADR-0012) · borda HTTP é a UX primária; **a CLI embutida foi retirada** (ADR-0037) — validação E2E é feita via Bruno (ADR-0034/0038).
> **Regras invariantes:** sempre `pnpm`, nunca `npm` (ADR-0012) · borda HTTP é a UX primária; **a CLI embutida foi retirada** (ADR-0037) — validação E2E é feita em **Hurl** (ADR-0074; substituiu o Bruno em 2026-10-06).

---

Expand Down Expand Up @@ -70,7 +70,7 @@ src/

tests/ modules (mirror de src/) · cleanup (invariantes estruturais) · e2e · etl · infra · workers · jobs …
db/drizzle/ configs do drizzle-kit por módulo (contracts, auth, partners, programs, financial, notifications)
scripts/ ci · claude · e2e (Bruno) · etl · financial · handbook · seed · setup
scripts/ ci · claude · e2e (smoke HTTP) · etl · financial · handbook · seed · setup

handbook/ Source of Truth — interno ao repo
├── architecture/adr/ ADRs aceitos (IMUTÁVEIS)
Expand Down Expand Up @@ -146,10 +146,12 @@ pnpm run worker:supplier-projection # projeção fin_supplier_view
pnpm run job:migrate # aplica migrations
pnpm run job:contracts:sweep # oneshot: varredura de ciclo de vida de contratos

# Testes de integração (sobem MySQL/MinIO via Docker compose --wait) e E2E HTTP (Bruno)
# Testes de integração (sobem MySQL/S3 via Docker compose --wait) e smoke E2E HTTP
pnpm run test:integration:financial # idem :contracts :auth :partners :programs :notifications :storage :etl …
pnpm run test:e2e:auth # coleções .bru (idem :contracts :collaborators)
pnpm run test:integration:all # bruno-all.sh
pnpm run test:e2e:auth # smoke bash contra server real (idem :contracts :collaborators)

# Suíte da borda em Hurl (ADR-0074) — fora do git, não é gate: ver api-collections-hurl/README.md
hurl --test --jobs 1 --variables-file api-collections-hurl/_vars.env api-collections-hurl/casos/**/*.hurl

# Migrations (Drizzle Kit) — uma config por módulo em db/drizzle/
pnpm run db:generate # contracts (idem :auth :partners :programs :financial :notifications)
Expand Down
17 changes: 0 additions & 17 deletions api-collections/core-api/0-auth/00-health.bru

This file was deleted.

25 changes: 0 additions & 25 deletions api-collections/core-api/0-auth/01-register-bare-user.bru

This file was deleted.

35 changes: 0 additions & 35 deletions api-collections/core-api/0-auth/02-login-admin.bru

This file was deleted.

32 changes: 0 additions & 32 deletions api-collections/core-api/0-auth/03-login-bare.bru

This file was deleted.

32 changes: 0 additions & 32 deletions api-collections/core-api/0-auth/04-login-reader.bru

This file was deleted.

32 changes: 0 additions & 32 deletions api-collections/core-api/0-auth/05-login-contracts-operator.bru

This file was deleted.

32 changes: 0 additions & 32 deletions api-collections/core-api/0-auth/06-login-partners-operator.bru

This file was deleted.

32 changes: 0 additions & 32 deletions api-collections/core-api/0-auth/07-login-bare-user.bru

This file was deleted.

32 changes: 0 additions & 32 deletions api-collections/core-api/0-auth/08-login-programs-operator.bru

This file was deleted.

Loading
Loading