- 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>
20 KiB
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 emdocs/(capítulos00–23).
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 emlibs/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
tsconfigbase:noUncheckedIndexedAccess,module/moduleResolution: NodeNext,experimentalDecorators+emitDecoratorMetadata,target: ES2023. → §01openapi.jsonemlibs/shared/api-interface/; CI bloqueia PR sem bump de versão na URL. → §05
Boot, config & secrets
import './tracing'como primeiro import demain.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 pushem prod); rodam antes do rolling deploy via URL direta (5432, sem PgBouncer). → §03, §18 - Trabalho assíncrono (email, webhook, processamento, agendado) via BullMQ;
jobIddeterminístico;queue.adddepois doprisma.$transaction. → §11 - POST financeiro/crítico (pagamentos, pedidos) exige header
Idempotency-Keymemoizado por 24h. → §05
Auth & rate limit
- Refresh em cookie
httpOnly; Secure; SameSite=Lax; Path=/api/v1/auth/refresh; access token em memória (nuncalocalStorage). → §07 - Senha mínimo 12 chars +
zxcvbn/haveibeenpwned; hash argon2idm=65536, t=3, p=4(defaults da libargon2@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; chaveusers/<userId>/; nunca proxy upload/download pelo backend. → §14 - Frontend Vite servido por Nginx em VM Proxmox + Cloudflare;
Cache-Control: immutableem assets versionados,no-cache+ purge Cloudflare emindex.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). Semlayer:*(deprecado). CI rodapnpm nx affected -t lintpara validar boundaries. → §02 - Dependências compartilhadas (
zod,react,typescript) via pnpm catalogs ("zod": "catalog:"). Named exports em libs;index.tsexpõe a API pública. → §01, §02 - Feature flags em
libs/shared/feature-flagscomowner+expiresAtobrigatórios; CI falha em flag expirada, sem owner ou chave duplicada. Prefixosrel-/exp-/ops-/perm-. → §10
Observabilidade & resiliência
- Logs com Pino estruturado +
redactpara*.cpf/*.cardNumber/*.password/authorization/cookie; semconsole.log. → §09 - OTel sampling head-based 10% (
OTEL_TRACES_SAMPLER=parentbased_traceidratio,ARG=0.1). Sentry React commaskAllText: 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 doWorkspacePrismaPool+ Valkey + BullMQ — nunca pingar os N workspaces); dreno 15s no shutdown,stop_grace_period: 30sno docker-compose, HAProxy drain de 30s antes docompose 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 comfetch-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 viacreateTestClient(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-ignoresem 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-001Long-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 combound_claimsconfinado a repo+branch. → §18.3, §08.6 - ❌
PGD-SEC-002Secret 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]+)nodist/. → §08.6 - ❌
PGD-SEC-003VITE_*_SECRET/ server key / SMTP / token-de-escrita emVITE_*— bundle é público. Permitido sóVITE_API_URL, public keys, Sentry DSN. → §08.8 - ❌
PGD-CICD-001Imagem Alpine semapk add --no-cache openssl— Prisma e sharp crasham. → §18.5
Backend / Prisma
- ❌
PGD-DB-001prisma migratevia PgBouncer transaction pooling — Schema Engine quebra; useMIGRATION_DATABASE_URLdireta (5432). → §03.3 - ❌
PGD-DB-002Modelo de multi-tenancy errado (row-leveltenantId,SET search_path, schema-per-tenant) — o stack adota BD-por-workspace (ADR 0006): cada workspace tem cluster PG próprio, resolvido viaget_workspace_connectionno master-login; modelos de domínio não declaramworkspaceId/tenantId. → §03.4 - ❌
PGD-DB-003prismaemdevDependencies— quebra apóspnpm deploy --prod; vai emdependencies. → §03.5 - ❌
PGD-DB-004moduleFormat = "esm"no generator Prisma — NestJS é CJS; use"cjs". → §03.2 - ❌
PGD-DB-005ADD COLUMN NOT NULL DEFAULTem tabela grande — full table scan sobAccessExclusiveLock; use 3 deploys (nullable → backfill em job →CHECK NOT VALID+VALIDATE+SET NOT NULL). → §03.3 - ❌
PGD-DB-006DROP COLUMNno mesmo PR que remove o uso — migrate roda antes do rolling swap das VMs; app v1 quebra comP2022durante a janela em que VM antiga ainda recebe tráfego. Faça em 2 deploys (remover uso primeiro, drop depois). → §03.3 - ❌
PGD-DB-007RENAME COLUMN/RENAME TABLEdireto — app v1 ainda em tráfego usa o nome antigo. Padrão add → dual-write + backfill → drop em 3 deploys. → §03.3 - ❌
PGD-DB-008CREATE INDEXsemCONCURRENTLYem tabela quente —ShareLocksegura todas as escritas até terminar; useCREATE INDEX CONCURRENTLYem migration fora de transação. → §03.3 - ❌
PGD-DB-009import { prisma }singleton ounew PrismaClient()direto em service de domínio — sob BD-por-workspace (ADR 0006),PrismaClienté resolvido por request viacls.get('prisma'); singleton acerta o BD errado (master-login/default) e quebra o isolamento. Lint emlibs/api/util-eslint-authzproíbe fora deutil-prisma-workspacee dos nomesauditPrisma/billingPrisma. → §03.4 - ❌
PGD-HTTP-001Retornar entidades Prisma cruas — sempre mapear via Zod schema emlibs/shared/api-interface. → §03.3
Auth
- ❌
PGD-AUTH-001JWT emlocalStorage; refresh sem family revocation;client_secretOAuth no frontend; token de reset/refresh cru no DB. → §07.3 - ❌
PGD-AUTH-002Cookie de refresh semPath=/api/v1/auth/refreshexato —Pathlargo (/,/api) vaza credencial de 30d em logs/proxies;Pathestreito (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-005JWT_SECRETHS256 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-007App cliente mantendousers.passwordHashpró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-001findUnique({ where: { id } })em service de domínio usandoPrismaClientsingleton — query acerta o BD errado (master-login/default) e quebra isolamento cross-workspace; sempre injetar viacls.get('prisma'). Defense-in-depth intra-workspace:assertOwnership(resource, user). → §23.4 - ❌
PGD-AUTHZ-002workspace_id/ownerIdvindo 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-003Decisão de autorização só no frontend — backend re-decide em todas as ações; UI só esconde. → §23.8 - ❌
PGD-AUTHZ-005$queryRaw/$executeRawemPrismaClientsingleton ou emauditPrisma/billingPrismadentro de service de domínio — atravessa workspaces sem aviso. Sempre rodar emcls.get('prisma'); cross-workspace deliberado usa nome explícito emdata-access-audit/data-access-billing. → §23.4 - ❌
PGD-AUTHZ-006Job BullMQ semworkspaceIdno payload — worker não temreq; sem repopular o CLS (cls.runWith({ workspaceId, prisma: pool.getOrCreate(...) }, handler)), o handler crasha ou (com singleton acidental) escreve no BD errado. Producer sempre incluiworkspaceId; worker sempre recria o CLS. → §23.4, §11
Filas / jobs
- ❌
PGD-FILA-001Valkey commaxmemory-policydiferente denoeviction(LRU/LFU/random) — descarta jobs em pressão de memória. Configurenoevictionnovalkey.conf+ alerta de memória ≥80%. → §11.2 - ❌
PGD-FILA-002@nestjs/scheduleem multi-instância (>1 VM/container rodando o backend ou worker) — dispara em todas. Use BullMQ Job Schedulers (comtimeZone: 'America/Sao_Paulo'). → §11.2 - ❌
PGD-FILA-003Job semjobIddeterminístico — sem idempotência em retries / webhooks at-least-once. → §11.9 - ❌
PGD-FILA-004Email/webhook síncrono em handler HTTP — sempre via BullMQ. → §11.4, §15.3 - ❌
PGD-FILA-005queue.addantes do commit Prisma —queue.addconfirma no Valkey mesmo se a transação reverter, deixando o job órfão. → §11.4, §20.8 - ❌
PGD-FILA-006@nestjs/event-emitterpara evento de domínio cross-instância — não atravessa containers/VMs, sem retry. Use BullMQ. → §21.10
Cache & CDN
- ❌
PGD-CACHE-001TTL em segundos com cache-manager 6 — agora é milissegundos (breaking vs v5). → §12.4 - ❌
PGD-CACHE-002CacheInterceptorgenérico em rotas autenticadas — chave é a URL, vaza dados entre usuários/workspaces. Use chave manualuser:<id>:.... → §12.6 - ❌
PGD-CACHE-003Cachear endpoints autenticados na borda (Cloudflare) / esquecerVary: Authorizationem cache compartilhado. Cloudflare deve bypassar/api/*por default. → §12.9
Real-time & uploads
- ❌
PGD-RT-001Token em query string para auth WebSocket — vaza em access logs. Use ticket single-use 30-60s emitido por endpoint REST. → §13.5 - ❌
PGD-RT-002Handler@SubscribeMessagelendo BD sem reconstruir CLS — o upgrade WS roda fora do pipeline HTTP, entãoWorkspaceContextMiddlewarenunca executou ecls.get('prisma')retornaundefined(crash) ou, pior, importarPrismaClientsingleton acerta o BD do master-login (PGD-DB-009). Padrão: para escrita, enfileirar BullMQ comworkspaceIdno payload (PGD-AUTHZ-006); para leitura síncrona no handler,cls.runWith({ workspaceId: client.data.workspaceId, prisma: pool.getOrCreate(dbConfig) }, handler)usando oWorkspacePrismaPoolda app. → §13.4 - ❌
PGD-UPL-001Presigned PUT sem POST policy — não travacontent-length/content-type; usecreatePresignedPostcomConditions. → §14.5 - ❌
PGD-UPL-002CORS do bucket MinIO esquecido — PUT/POST quebra silenciosamente; configure viamcna criação do bucket e teste em CI. → §14.12
Observabilidade & LGPD
- ❌
PGD-OBS-001import './tracing'depois do Nest —tracing.ts(NodeSDK) deve ser primeiro import demain.ts. → §09.3 - ❌
PGD-OBS-002redactPino sem*.cpf,*.cardNumber,*.password,req.headers.authorization,req.headers.cookie— LGPD exige redact agressivo. → §09.3 - ❌
PGD-OBS-003/health/readypingando todos os workspaces ativos (for (const ws of workspaces) ping(ws)) — escala O(N) e marca o containerunhealthyquando 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 doWorkspacePrismaPool(K=3 default); workspaces individuais com problema vivem emdb.connections{workspace}(alerta), não em readiness. → §20.12
- ❌
PGD-EMAIL-001Sem webhook do provedor (Resend) + alarme Grafana (bounce >5% / complaint >0.1%) — provedor suspende o domínio. → §15.5 - ❌
PGD-EMAIL-002Domí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-001Decisã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-002Email, CPF ou telefone como atributo de targeting — hashearuserId(SHA-256 + salt). → §10.8
CI/CD
- ❌
PGD-CICD-002Actions GitHub por tag (use SHA); taglatestem prod; workflow semfetch-depth: 0(nx affectedquebra). → §18.4 - ❌
PGD-CICD-003Containerizar 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.