Files
sar/CODING-RULES.md
julian 17c08e6392 chore: initial monorepo scaffold + WDS Phase 1+2 artifacts
- Nx 22.7 monorepo (pnpm 11.1, TypeScript 5.9, Node 24)
- apps/api: NestJS 11 (CJS conforme CODING-RULES.md PGD-DB-004)
- apps/web: React 19 + Vite 8 (ESM)
- libs/shared/api-interface: Zod contract base
- Docker Compose dev: Postgres 18, Valkey 8, MinIO, Mailpit
- WDS artifacts:
  - design-artifacts/A-Product-Brief/ (5 docs canônicos + 16 dialogs)
  - design-artifacts/B-Trigger-Map/ (hub + 4 personas + feature impact)
- Stack canon: STACK.md v2.2 + CODING-RULES.md v2.0 + brand.md
- AGENTS.md + README.md como entrada para devs/agentes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:34:20 +00:00

20 KiB
Raw Permalink Blame History

CODING-RULES.md — Regras de Geração de Código

Define como escrever o código: invariantes obrigatórias e armadilhas críticas. Leia antes de gerar ou alterar qualquer código. Companion de STACK.md (tecnologias e versões). Documentação completa em docs/ (capítulos 0023).

1. Invariantes (constituição — não mudam sem RFC)

Tipos & contrato

  • Schema Zod compartilhado entre Nest e React (DTO via createZodDto, nunca duplicado). Schemas em *.schema.ts; DTOs em *.dto.ts; ambos em libs/shared/api-interface. → §06
  • Erros HTTP em RFC 9457 Problem Details; Zod → 422 (nunca 400). 403 vira 404 quando o cliente não tem acesso (não vazar existência). → §05
  • tsconfig base: noUncheckedIndexedAccess, module/moduleResolution: NodeNext, experimentalDecorators + emitDecoratorMetadata, target: ES2023. → §01
  • openapi.json em libs/shared/api-interface/; CI bloqueia PR sem bump de versão na URL. → §05

Boot, config & secrets

  • import './tracing' como primeiro import de main.ts (OTel monkey-patch antes do Nest). → §09
  • Config Nest validada com Zod (validate: env => EnvSchema.parse(env)) — fail-fast no boot. → §08
  • Secrets via HashiCorp Vault (KV v2); runtime via Vault Agent injetando env no entrypoint do container. Nunca env hardcoded ou embutido no bundle. → §08

Dados, jobs & side-effects

  • Mudanças em DB via migration Prisma (expand/contract, backward-compatible, sem db push em prod); rodam antes do rolling deploy via URL direta (5432, sem PgBouncer). → §03, §18
  • Trabalho assíncrono (email, webhook, processamento, agendado) via BullMQ; jobId determinístico; queue.add depois do prisma.$transaction. → §11
  • POST financeiro/crítico (pagamentos, pedidos) exige header Idempotency-Key memoizado por 24h. → §05

Auth & rate limit

  • Refresh em cookie httpOnly; Secure; SameSite=Lax; Path=/api/v1/auth/refresh; access token em memória (nunca localStorage). → §07
  • Senha mínimo 12 chars + zxcvbn/haveibeenpwned; hash argon2id m=65536, t=3, p=4 (defaults da lib argon2@0.44.0, centralizado no master-login). Refresh token com family revocation e hash no DB (32 bytes base64url, single-use, TTL 15-30 min). → §07
  • Rate limit em /login, /signup, /forgot-password: 5/min/IP + 10/h/email via @nestjs/throttler (Valkey). → §07, §20

Frontend & UX

  • Estado de servidor em TanStack Query; estado de formulário em react-hook-form; cliente em Zustand. Não misturar. → §04
  • Upload via MinIO presigned POST policy (S3-compat) com content-length-range + starts-with $Content-Type; chave users/<userId>/; nunca proxy upload/download pelo backend. → §14
  • Frontend Vite servido por Nginx em VM Proxmox + Cloudflare; Cache-Control: immutable em assets versionados, no-cache + purge Cloudflare em index.html. → §18

Monorepo & flags

  • Toda lib nova declara as três tags scope:api|web|shared + type:app|domain|feature|data-access|ui|util + domain:<nome>|shared (ADR 0002). Sem layer:* (deprecado). CI roda pnpm nx affected -t lint para validar boundaries. → §02
  • Dependências compartilhadas (zod, react, typescript) via pnpm catalogs ("zod": "catalog:"). Named exports em libs; index.ts expõe a API pública. → §01, §02
  • Feature flags em libs/shared/feature-flags com owner + expiresAt obrigatórios; CI falha em flag expirada, sem owner ou chave duplicada. Prefixos rel- / exp- / ops- / perm-. → §10

Observabilidade & resiliência

  • Logs com Pino estruturado + redact para *.cpf/*.cardNumber/*.password/authorization/cookie; sem console.log. → §09
  • OTel sampling head-based 10% (OTEL_TRACES_SAMPLER=parentbased_traceidratio, ARG=0.1). Sentry React com maskAllText: true, blockAllMedia: true, tracePropagationTargets (W3C). → §09
  • Métricas mínimas: p50/p95/p99, error rate, queue depth, DB pool, heap/RSS, cache hit ratio. Alertas derivados dos SLOs default por tier (ADR 0001): CRUD p99 >800ms/5min, agregação p99 >2s/5min, error >1%/5min, queue >1000/10min, DB pool >80%/5min, bounce rate do provedor de email >5% (Resend). → §09
  • Health: /health/live (só memory.checkHeap(350MB)) + /health/ready (master-login + amostra LRU dos K pools quentes do WorkspacePrismaPool + Valkey + BullMQ — nunca pingar os N workspaces); dreno 15s no shutdown, stop_grace_period: 30s no docker-compose, HAProxy drain de 30s antes do compose up -d. → §20, §18

CI/CD & testes

  • Mesma image digest promovida dev→staging→prod; tag por commit SHA, nunca latest. → §18
  • Actions GitHub pinadas por commit SHA (não tag); Trivy gate (severity: CRITICAL,HIGH, exit-code: 1); workflows com fetch-depth: 0 + nrwl/nx-set-shas@v4. → §18
  • Pirâmide ~70% unit / ~25% integração / ~5% E2E; integração com Testcontainers (Postgres real postgres:18-alpine, Valkey quando exercitado); HTTP via createTestClient (libs/api/util-testing), nunca import direto de supertest. → §16
  • Conventional Commits (commitlint enforça): feat, fix, chore, docs, refactor, test, perf, style, ci, build. → §19
  • Sem console.log / any / @ts-ignore sem justificativa. → §17

2. Pegadinhas 🔥 críticas (quebram produção / vazam dados / violam LGPD)

Catálogo curto — apenas o que quebra prod, vaza dados ou viola LGPD. Pegadinhas de menor severidade (⚠️ inconsistência funcional, 💡 estilo) vivem nos capítulos canônicos em docs/.

Para adicionar/editar uma pegadinha: leia primeiro §19b — Guia de estilo editorial (template canônico, IDs PGD-<DOMÍNIO>-<NNN>, severidades 🔥/⚠️/💡, fluxo de contribuição).

Secrets, infra & imagem

  • PGD-SEC-001 Long-lived credentials em GitHub Secrets (*_ACCESS_KEY, senhas, tokens sem expiração) — use OIDC GitHub → Vault JWT auth → SSH cert efêmero (TTL 5min) ou tokens de curtíssima duração emitidos por role com bound_claims confinado a repo+branch. → §18.3, §08.6
  • PGD-SEC-002 Secret runtime embutido em build/bundle Docker — runtime via Vault Agent no entrypoint do container; CI scaneia (AKIA[0-9A-Z]{16}|eyJ[A-Za-z0-9_-]{30,}|re_[A-Za-z0-9]{20,}|hvs\.[A-Za-z0-9]+) no dist/. → §08.6
  • PGD-SEC-003 VITE_*_SECRET / server key / SMTP / token-de-escrita em VITE_* — bundle é público. Permitido só VITE_API_URL, public keys, Sentry DSN. → §08.8
  • PGD-CICD-001 Imagem Alpine sem apk add --no-cache openssl — Prisma e sharp crasham. → §18.5

Backend / Prisma

  • PGD-DB-001 prisma migrate via PgBouncer transaction pooling — Schema Engine quebra; use MIGRATION_DATABASE_URL direta (5432). → §03.3
  • PGD-DB-002 Modelo de multi-tenancy errado (row-level tenantId, SET search_path, schema-per-tenant) — o stack adota BD-por-workspace (ADR 0006): cada workspace tem cluster PG próprio, resolvido via get_workspace_connection no master-login; modelos de domínio não declaram workspaceId/tenantId. → §03.4
  • PGD-DB-003 prisma em devDependencies — quebra após pnpm deploy --prod; vai em dependencies. → §03.5
  • PGD-DB-004 moduleFormat = "esm" no generator Prisma — NestJS é CJS; use "cjs". → §03.2
  • PGD-DB-005 ADD COLUMN NOT NULL DEFAULT em tabela grande — full table scan sob AccessExclusiveLock; use 3 deploys (nullable → backfill em job → CHECK NOT VALID + VALIDATE + SET NOT NULL). → §03.3
  • PGD-DB-006 DROP COLUMN no mesmo PR que remove o uso — migrate roda antes do rolling swap das VMs; app v1 quebra com P2022 durante a janela em que VM antiga ainda recebe tráfego. Faça em 2 deploys (remover uso primeiro, drop depois). → §03.3
  • PGD-DB-007 RENAME COLUMN/RENAME TABLE direto — app v1 ainda em tráfego usa o nome antigo. Padrão add → dual-write + backfill → drop em 3 deploys. → §03.3
  • PGD-DB-008 CREATE INDEX sem CONCURRENTLY em tabela quente — ShareLock segura todas as escritas até terminar; use CREATE INDEX CONCURRENTLY em migration fora de transação. → §03.3
  • PGD-DB-009 import { prisma } singleton ou new PrismaClient() direto em service de domínio — sob BD-por-workspace (ADR 0006), PrismaClient é resolvido por request via cls.get('prisma'); singleton acerta o BD errado (master-login/default) e quebra o isolamento. Lint em libs/api/util-eslint-authz proíbe fora de util-prisma-workspace e dos nomes auditPrisma/billingPrisma. → §03.4
  • PGD-HTTP-001 Retornar entidades Prisma cruas — sempre mapear via Zod schema em libs/shared/api-interface. → §03.3

Auth

  • PGD-AUTH-001 JWT em localStorage; refresh sem family revocation; client_secret OAuth no frontend; token de reset/refresh cru no DB. → §07.3
  • PGD-AUTH-002 Cookie de refresh sem Path=/api/v1/auth/refresh exato — Path largo (/, /api) vaza credencial de 30d em logs/proxies; Path estreito (sem o prefixo global /api/v1) faz o navegador não enviar o cookie e o refresh quebra em prod (401 silencioso). → §07.3
  • PGD-AUTH-005 JWT_SECRET HS256 compartilhada entre apps cliente do master-login — uma app comprometida forja tokens válidos para qualquer outra do workspace (impersonação cross-app). Distribuir só via Vault Agent enquanto RS256 não fecha. → §07
  • PGD-AUTH-007 App cliente mantendo users.passwordHash próprio em vez de delegar ao master-login — credenciais divergem na 1ª troca de senha + vazamento da app expõe hashes que deveriam viver só no IdP. → §07

Autorização

  • PGD-AUTHZ-001 findUnique({ where: { id } }) em service de domínio usando PrismaClient singleton — query acerta o BD errado (master-login/default) e quebra isolamento cross-workspace; sempre injetar via cls.get('prisma'). Defense-in-depth intra-workspace: assertOwnership(resource, user). → §23.4
  • PGD-AUTHZ-002 workspace_id/ownerId vindo do body/param/query do cliente — sempre do JWT (req.user.workspace_id, req.user.sub); administração cross-workspace é função do master-login, não das apps cliente. → §23.3
  • PGD-AUTHZ-003 Decisão de autorização só no frontend — backend re-decide em todas as ações; UI só esconde. → §23.8
  • PGD-AUTHZ-005 $queryRaw/$executeRaw em PrismaClient singleton ou em auditPrisma/billingPrisma dentro de service de domínio — atravessa workspaces sem aviso. Sempre rodar em cls.get('prisma'); cross-workspace deliberado usa nome explícito em data-access-audit/data-access-billing. → §23.4
  • PGD-AUTHZ-006 Job BullMQ sem workspaceId no payload — worker não tem req; sem repopular o CLS (cls.runWith({ workspaceId, prisma: pool.getOrCreate(...) }, handler)), o handler crasha ou (com singleton acidental) escreve no BD errado. Producer sempre inclui workspaceId; worker sempre recria o CLS. → §23.4, §11

Filas / jobs

  • PGD-FILA-001 Valkey com maxmemory-policy diferente de noeviction (LRU/LFU/random) — descarta jobs em pressão de memória. Configure noeviction no valkey.conf + alerta de memória ≥80%. → §11.2
  • PGD-FILA-002 @nestjs/schedule em multi-instância (>1 VM/container rodando o backend ou worker) — dispara em todas. Use BullMQ Job Schedulers (com timeZone: 'America/Sao_Paulo'). → §11.2
  • PGD-FILA-003 Job sem jobId determinístico — sem idempotência em retries / webhooks at-least-once. → §11.9
  • PGD-FILA-004 Email/webhook síncrono em handler HTTP — sempre via BullMQ. → §11.4, §15.3
  • PGD-FILA-005 queue.add antes do commit Prisma — queue.add confirma no Valkey mesmo se a transação reverter, deixando o job órfão. → §11.4, §20.8
  • PGD-FILA-006 @nestjs/event-emitter para evento de domínio cross-instância — não atravessa containers/VMs, sem retry. Use BullMQ. → §21.10

Cache & CDN

  • PGD-CACHE-001 TTL em segundos com cache-manager 6 — agora é milissegundos (breaking vs v5). → §12.4
  • PGD-CACHE-002 CacheInterceptor genérico em rotas autenticadas — chave é a URL, vaza dados entre usuários/workspaces. Use chave manual user:<id>:.... → §12.6
  • PGD-CACHE-003 Cachear endpoints autenticados na borda (Cloudflare) / esquecer Vary: Authorization em cache compartilhado. Cloudflare deve bypassar /api/* por default. → §12.9

Real-time & uploads

  • PGD-RT-001 Token em query string para auth WebSocket — vaza em access logs. Use ticket single-use 30-60s emitido por endpoint REST. → §13.5
  • PGD-RT-002 Handler @SubscribeMessage lendo BD sem reconstruir CLS — o upgrade WS roda fora do pipeline HTTP, então WorkspaceContextMiddleware nunca executou e cls.get('prisma') retorna undefined (crash) ou, pior, importar PrismaClient singleton acerta o BD do master-login (PGD-DB-009). Padrão: para escrita, enfileirar BullMQ com workspaceId no payload (PGD-AUTHZ-006); para leitura síncrona no handler, cls.runWith({ workspaceId: client.data.workspaceId, prisma: pool.getOrCreate(dbConfig) }, handler) usando o WorkspacePrismaPool da app. → §13.4
  • PGD-UPL-001 Presigned PUT sem POST policy — não trava content-length/content-type; use createPresignedPost com Conditions. → §14.5
  • PGD-UPL-002 CORS do bucket MinIO esquecido — PUT/POST quebra silenciosamente; configure via mc na criação do bucket e teste em CI. → §14.12

Observabilidade & LGPD

  • PGD-OBS-001 import './tracing' depois do Nest — tracing.ts (NodeSDK) deve ser primeiro import de main.ts. → §09.3
  • PGD-OBS-002 redact Pino sem *.cpf, *.cardNumber, *.password, req.headers.authorization, req.headers.cookie — LGPD exige redact agressivo. → §09.3
  • PGD-OBS-003 /health/ready pingando todos os workspaces ativos (for (const ws of workspaces) ping(ws)) — escala O(N) e marca o container unhealthy quando um workspace dormente está lento, retirando o nó inteiro do HAProxy por culpa de um tenant que ninguém estava usando. Sob BD-por-workspace (ADR 0006), readiness pinga master-login (obrigatório) + amostra LRU dos K pools mais quentes do WorkspacePrismaPool (K=3 default); workspaces individuais com problema vivem em db.connections{workspace} (alerta), não em readiness. → §20.12

Email

  • PGD-EMAIL-001 Sem webhook do provedor (Resend) + alarme Grafana (bounce >5% / complaint >0.1%) — provedor suspende o domínio. → §15.5
  • PGD-EMAIL-002 Domínio sem DKIM+SPF+DMARC publicado (Gmail/Yahoo bloqueiam desde 2024) — vale qualquer provedor. Resend gera os CNAMEs prontos. → §15.6

Feature flags

  • PGD-FLAG-001 Decisão de negócio (preço, permissão, cobrança) avaliada só no client — sempre re-avaliar no backend; frontend só decide UI visual. → §10.10
  • PGD-FLAG-002 Email, CPF ou telefone como atributo de targeting — hashear userId (SHA-256 + salt). → §10.8

CI/CD

  • PGD-CICD-002 Actions GitHub por tag (use SHA); tag latest em prod; workflow sem fetch-depth: 0 (nx affected quebra). → §18.4
  • PGD-CICD-003 Containerizar o frontend Vite — sirva estático via Nginx em VM + Cloudflare. → §18.8

Ao atualizar uma 🔥 aqui, atualize a seção §NN.x correspondente no mesmo PR.


Regra de ouro para o agente: ao divergir das regras acima, pergunte antes e cite o capítulo de docs/ que justifica a alternativa. Não introduza tecnologia fora de STACK.md sem aprovação explícita.