Padrões de codificação e qualidade
Status: corrigir · Escopo: Padrões de código, camadas e organização do monorepo · Atualizado em: 2026-06-21
Normas obrigatórias para o
voltron. Objetivo: monorepo coeso, domínios com fronteiras rígidas, testes confiáveis e CI que bloqueia regressão. Alinhado aoblueprint.md(§4.4, §10, §20) e ao roadmap (E0.T1–T2).
1. Princípios
Seção intitulada “1. Princípios”| Princípio | Regra |
|---|---|
| Modular monolith | Um deploy Worker + um serviço LLM; separação lógica por bounded context, não por microserviço prematuro |
| TS orquestra, Python gera | Workers decidem estratégia; Python faz NLU/geração — sem lógica de negócio duplicada entre runtimes |
| Domínio no centro | Regras de negócio vivem em domain/; infra (storage, filas, HTTP) fica nas bordas |
| Contratos explícitos | Toda fronteira (domínio↔domínio, Worker↔LLM, Worker↔DB) tem tipo/schema versionado |
| Fail closed | Consent, privacy, safety e idempotência negam por padrão — nunca “seguir em frente” em dúvida |
| Observabilidade by default | Toda decisão relevante emite timeline_event; toda chamada LLM gera llm_call |
| Zero import acidental | Fronteiras de import são verificadas automaticamente (CI), não por review humano |
2. Layout do monorepo
Seção intitulada “2. Layout do monorepo”/├── apps/│ ├── worker/ # Cloudflare Workers (TypeScript)│ │ └── src/│ │ ├── channel/│ │ ├── identity/│ │ ├── conversation/│ │ ├── orchestration/│ │ ├── policy/│ │ ├── workflow/│ │ ├── memory/│ │ ├── knowledge/│ │ ├── privacy/│ │ ├── safety/│ │ ├── actions/│ │ ├── quality/│ │ ├── storage/ # repositórios Postgres/R2 — única camada com SQL│ │ ├── observability/│ │ ├── jobs/ # consumers de fila│ │ └── api/ # rotas HTTP (webhooks, health, admin)│ └── llm/ # Python — Pydantic AI│ └── src/│ ├── prompting/│ ├── grounding/│ ├── media/│ ├── guardrails/│ ├── observability/│ ├── evals/│ └── schemas/ # contratos Pydantic (fonte de verdade semântica)├── packages/│ ├── contracts/ # JSON Schema / OpenAPI gerados → tipos TS│ └── test-fixtures/ # factories, seeds, golden payloads├── schema/│ └── migrations/ # DDL versionado (data-model-v1)├── docs/│ ├── engineering/ # este doc + ADRs│ └── architecture/├── pyproject.toml # uv workspace root├── pnpm-workspace.yaml├── turbo.json└── biome.json / eslint.config.*Regras de pasta
- Cada bounded context em
apps/api-worker/src/<context>/segue a mesma forma interna (§3). - Nada de lógica de domínio em
api/oujobs/— só adaptadores (parse, auth, enqueue, dispatch). - SQL somente em
apps/api-worker/src/storage/(TS) e, se necessário no futuro,apps/llm-service/src/observability/para writes isolados dellm_calls. - Arquivos compartilhados entre contextos TS →
packages/contractsou tipos emstorageexpostos via interface — nunca import cruzado dedomain/de outro contexto.
3. Anatomia de um bounded context (Worker)
Seção intitulada “3. Anatomia de um bounded context (Worker)”<context>/├── domain/ # entidades, value objects, regras puras (sem I/O)├── application/ # use cases / services — orquestra domain + ports├── ports/ # interfaces (repositórios, gateways externos)└── index.ts # public API do módulo — único ponto de export externo| Camada | Pode importar | Não pode importar |
|---|---|---|
domain/ | outros domain/ do mesmo contexto, tipos de packages/contracts | storage, api, jobs, outros contextos, SDKs (Meta, Neon, R2) |
application/ | domain/, ports/ do mesmo contexto, packages/contracts | implementações concretas de storage, outros contextos (exceto via port injetada) |
ports/ | tipos de domínio, contratos | implementações |
index.ts | re-exporta só o que outros contextos precisam | — |
Regra de ouro: outro contexto só importa de @worker/<context> (barrel index.ts). Importar de @worker/<context>/domain/... ou @worker/<context>/application/... → proibido (lint falha).
3.1 Grafo de dependências entre contextos (Worker)
Seção intitulada “3.1 Grafo de dependências entre contextos (Worker)”Espelha blueprint.md §10. Setas = “pode depender de”.
flowchart TD channel --> identity --> conversation conversation --> orchestration memory --> orchestration knowledge --> orchestration policy --> orchestration workflow --> orchestration privacy --> policy orchestration --> safety safety --> actions orchestration --> observability observability --> quality
storage -.->|implementa ports| channel storage -.->|implementa ports| identity storage -.->|implementa ports| conversation storage -.->|implementa ports| memory storage -.->|implementa ports| orchestration storage -.->|implementa ports| observability| Contexto | Depende de | Observação |
|---|---|---|
channel | identity, storage, observability | Gateway + idempotência inbound |
identity | storage, observability | Resolução tenant/unit/contact |
conversation | storage, observability | Estado, bursts, mensagens |
orchestration | conversation, memory, knowledge, policy, workflow, storage, observability | Núcleo — não chama Meta/LLM direto |
policy | privacy, storage | Regras configuráveis |
workflow | storage | Stage engine |
memory | storage, observability | 4 camadas + commit gate |
knowledge | storage | RAG index/retrieve |
privacy | storage | Consent, erasure |
safety | contrato LLM (HTTP), observability | Valida saída antes de actions |
actions | channel (outbound port), storage | Outbox + entrega |
quality | storage, observability | Eval/replay |
observability | storage | Writers de timeline/logs/llm_calls |
storage | — | Folha — nenhum contexto de domínio |
api, jobs | qualquer index.ts de contexto | Adaptadores finos |
Proibido
storageimportar qualquer contexto de domíniodomain/importar@cloudflare/workers-typesbindings diretamente — injetar viaports/orchestrationimportar SDK da Meta ou cliente HTTP do LLM — usar ports (LlmGateway,ChannelOutbound)- Ciclo entre contextos (A→B→A)
3.2 Python (apps/llm-service) — FastAPI
Seção intitulada “3.2 Python (apps/llm-service) — FastAPI”Stack HTTP: FastAPI + Uvicorn. Handlers finos; lógica em prompting/, guardrails/, etc.
apps/llm-service/src/tactflow_llm/├── api/ # FastAPI app, routes, deps (auth) — sem regra de negócio├── schemas/ # Pydantic — fonte de verdade dos contratos Worker↔LLM├── prompting/ # agents Pydantic AI├── grounding/ # embeddings + retrieval├── media/ # transcrição / visão├── guardrails/ # validação pós-geração├── observability/ # writer llm_calls└── evals/ # Pydantic Evals| Camada | Regra |
|---|---|
schemas/ | Sem dependência de FastAPI/HTTP; só Pydantic + stdlib |
api/ | Rotas FastAPI finas — validam schema, chamam service, retornam schema |
prompting/, guardrails/, etc. | Importam schemas/; não importam api/ |
Proibido em Python
- Reimplementar regras de orquestração (timing, burst, window 24h, consent) — isso é TS
- Acesso direto a tabelas que não sejam
llm_calls/ chunks de knowledge (se explicitado) - Import de código TypeScript
4. Contratos Worker ↔ LLM
Seção intitulada “4. Contratos Worker ↔ LLM”- Fonte semântica:
apps/llm-service/src/tactflow_llm/schemas/(Pydantic v2). - Derivação TS: gerar tipos em
packages/contractsvia script (pnpm contracts:gen) — JSON Schema ou OpenAPI. - Versionamento: campo
contract_versionem payloads críticos; breaking change = nova versão + compat temporária. - Transporte: HTTP via FastAPI (Worker →
LLM_SERVICE_URL) — body validado Pydantic nos dois lados; auth service-to-service viaX-API-Key.
Model routing (OpenRouter): uma API key, modelo por slot de pipeline (UNDERSTAND, GENERATE, GUARD, …) — defaults no código, override por OPENROUTER_MODEL_<SLOT>, override por tenant via policy_profiles (Phase 1). Ver apps/llm-service/README.md. Não usar um único OPENROUTER_MODEL global.
5. Erros: resposta tipada { error_code, message, retryable } — nunca string solta.
Endpoints mínimos v1 (nomes ilustrativos):
| Endpoint | Request | Response |
|---|---|---|
POST /v1/understand | burst normalizado + context pack | UnderstandingResult |
POST /v1/generate | plano + context pack + grounding | GenerationResult |
POST /v1/guard | texto candidato + contexto | GuardResult |
5. Acesso a dados
Seção intitulada “5. Acesso a dados”| Regra | Detalhe |
|---|---|
| Single SQL layer (TS) | Queries só em storage/<context>/ — um repositório por agregado |
| Tenant scope obrigatório | Toda query inclui tenant_id; unit-scoped inclui unit_id quando aplicável |
| Sem ORM pesado | SQL explícito ou query builder tipado fino (ex.: Drizzle/Kysely) — decisão no E0, mas nunca SQL inline fora de storage/ |
| Transações | Use case em application/ abre transação via UnitOfWork port; repos não iniciam transação sozinhos |
| Idempotência | Writes críticos passam por idempotency_keys — teste de integração obrigatório |
| Particionamento | Repos encapsulam created_at para routing de partição — callers não escolhem partição |
| R2 | Keys namespaced: {tenant_id}/{unit_id}/{conversation_id}/... — sem PII em path quando evitável |
Proibido
orchestration/application/decide.tscomsql`SELECT ...`- Retornar row bruto do DB para camada de API — mapear para tipos de domínio/contrato
6. TypeScript — toolchain
Seção intitulada “6. TypeScript — toolchain”| Ferramenta | Uso |
|---|---|
| pnpm | Package manager + workspaces |
| Turbo | Orquestração de build/test/lint |
| TypeScript 5.x | strict: true, noUncheckedIndexedAccess, exactOptionalPropertyTypes |
| Biome | Lint + format (preferido) ou ESLint flat + Prettier — um par, não dois |
| Vitest | Unit + integration (Workers: @cloudflare/vitest-pool-workers quando aplicável) |
| dependency-cruiser | Valida grafo de imports entre contextos |
6.1 tsconfig baseline (obrigatório)
Seção intitulada “6.1 tsconfig baseline (obrigatório)”{ "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, "noImplicitOverride": true, "noFallthroughCasesInSwitch": true, "forceConsistentCasingInFileNames": true, "moduleResolution": "bundler", "verbatimModuleSyntax": true }}6.2 Estilo
Seção intitulada “6.2 Estilo”- Named exports — evitar default export (exceto configs)
- Sem
any—unknown+ narrow;@typescript-eslint/no-explicit-any: error - Sem barrel files gigantes —
index.tsexporta só API pública (~≤15 exports) - Erros tipados —
Result<T, E>ou exceptions de domínio mapeadas em boundary; não engolir erro - Datas — sempre
Dateem domínio,timestamptzISO string nos contratos wire - IDs — tipo branded
TenantId,ConversationId(UUID v7 string) — nãostringcru
6.3 Cloudflare Workers
Seção intitulada “6.3 Cloudflare Workers”- Handlers HTTP ≤ ~30 linhas — delegam para use case
- Sync boundary curta no webhook (ack + enqueue) — regra do blueprint §4.7
- DO (
ConversationRuntimeDO) — estado efêmero + coordenação; persistência via use cases, não lógica de negócio inline no DO - Bindings injetados — não acessar
envglobalmente dentro dedomain/
7. Python — toolchain (Astral)
Seção intitulada “7. Python — toolchain (Astral)”| Ferramenta | Uso |
|---|---|
| uv | Projeto, lock, scripts, CI |
| Ruff | Lint + format (ruff check, ruff format) |
| ty | Type check (ty check) |
| pytest | Testes |
| pytest-cov | Cobertura |
| import-linter | Fronteiras entre pacotes Python |
7.1 Config Ruff (baseline)
Seção intitulada “7.1 Config Ruff (baseline)”[tool.ruff.lint]select = ["E", "F", "I", "UP", "B", "SIM", "TCH", "RUF", "ASYNC", "S"] # S = bandit rulesignore = []
[tool.ruff.lint.isort]known-first-party = ["tactflow_llm"]
[tool.ruff.format]quote-style = "double"7.2 Estilo Python
Seção intitulada “7.2 Estilo Python”- Python ≥3.12
- Tipagem explícita em funções públicas;
from __future__ import annotations - Pydantic models para I/O; dataclasses/
NamedTuplepara internals leves - Async onde houver I/O (HTTP LLM providers)
- Logging estruturado (JSON) — campos:
trace_id,tenant_id,conversation_id,model,latency_ms
8. Testes
Seção intitulada “8. Testes”8.1 Pirâmide
Seção intitulada “8.1 Pirâmide”| Nível | O quê | Onde | Gate |
|---|---|---|---|
| Unit | Regras puras (domain/), gates, parsers | Vitest / pytest | CI sempre |
| Contract | Schemas TS↔Pydantic, snapshots JSON | packages/contracts | CI sempre |
| Integration | Repos + Neon branch efêmera | schema/ harness + repo tests | CI sempre |
| Worker | Handlers, DO, queue consumer (pool Workers) | apps/api-worker | CI sempre |
| E2E | Webhook → fila → persist → outbound mock | poucos, críticos | CI main + nightly |
| Eval | Pydantic Evals golden sets | apps/llm-service/evals | CI main (amostra) + release |
8.2 Cobertura mínima (merge bloqueado se abaixo)
Seção intitulada “8.2 Cobertura mínima (merge bloqueado se abaixo)”| Escopo | Lines | Branches | Notas |
|---|---|---|---|
| Global repo | ≥ 80% | ≥ 70% | Exclui configs, generated, migrations |
domain/ + application/ | ≥ 90% | ≥ 80% | Onde mora a regra de negócio |
privacy/, safety/, idempotência | 100% | ≥ 95% | Fail-closed — sem exceção |
storage/ | ≥ 75% | — | Integration compensa |
Python guardrails/, schemas/ | ≥ 90% | ≥ 80% |
Exclusões permitidas no relatório (não na disciplina): arquivos *.generated.ts, schema/migrations/, **/index.ts re-export only.
8.3 Convenções de teste
Seção intitulada “8.3 Convenções de teste”- Arquivo espelha produção:
decide.ts→decide.test.ts/test_decide.py - Factories em
packages/test-fixtures— não copiar JSON inline em 10 testes - Nomenclatura:
describe('<Context>/<UseCase>')+it('should <behavior> when <condition>') - Testes de idempotência: mesmo input 2× → 1 side effect
- Testes de tenant isolation: tenant A nunca lê/escreve tenant B
- Sem teste que depende de ordem —
parallelsafe
8.4 Integração e colunas do schema
Seção intitulada “8.4 Integração e colunas do schema”- Integração: repos + Neon (
*.integration.test.ts,tests/integration/,@pytest.mark.integration) - Matriz de paths:
schema/integration-paths.yml— todo fluxo ponta a ponta mapeado a testes - Colunas:
pnpm schema:columns— 360 colunas classificadas (used|harness|system|deferred+ justificativa) - Nova coluna DDL → atualizar migration +
column-usage.yml+ refs de runtime
9. CI — quality gate (obrigatório em PR)
Seção intitulada “9. CI — quality gate (obrigatório em PR)”Ordem sugerida (falha rápida):
# .github/workflows/ci.yml (alvo pós-scaffold)jobs: contracts: # pnpm contracts:gen && git diff --exit-code ts-boundaries: # dependency-cruiser + eslint-plugin-boundaries ts: # biome check + tsc + vitest --coverage py-boundaries: # lint-imports py: # ruff check + ruff format --check + ty check + pytest --cov schema: # Neon branch → migrations → canonical_queries + invariants (E1.T4)| Check | Comando (raiz) | Bloqueia merge |
|---|---|---|
| Format/lint TS | pnpm lint | ✅ |
| Typecheck TS | pnpm typecheck | ✅ |
| Test + cov TS | pnpm test -- --coverage | ✅ |
| Import boundaries TS | pnpm boundaries | ✅ |
| Format/lint Py | uv run ruff check . && uv run ruff format --check . | ✅ |
| Typecheck Py | uv run ty check | ✅ |
| Test + cov Py | uv run pytest --cov --cov-fail-under=80 | ✅ |
| Import boundaries Py | uv run lint-imports | ✅ |
| Schema harness | pnpm schema:validate (dev branch) / CI schema:validate:ci | ✅ quando secrets Neon configurados |
| Column audit | pnpm schema:columns | ✅ |
| Integration (TS+Py) | pnpm test:integration | ✅ no job schema (Neon) |
| Contracts drift | pnpm contracts:gen && git diff --exit-code packages/contracts | ✅ |
Branch protection (GitHub): status check Quality gate (blocking) required; no direct push to main. Em repo privado sem Pro, enforce via review + CI verde obrigatório antes de merge.
10. Pre-commit (local)
Seção intitulada “10. Pre-commit (local)”Arquivo .pre-commit-config.yaml na raiz:
| Hook | Ferramenta |
|---|---|
| Trailing whitespace / EOF | pre-commit built-in |
| TS format+lint | Biome (staged files) |
| Py format+lint | Ruff (staged files) |
| Secrets | gitleaks ou detect-secrets |
| SQL migrations | sqlfluff lint (opcional v1) |
Instalação automática: pnpm install roda prepare → scripts/install-git-hooks.mjs (uv sync --group dev + pre-commit install). Manual: pnpm setup:hooks. Pule com SKIP_HOOKS=1.
11. Observabilidade no código
Seção intitulada “11. Observabilidade no código”Todo use case que decide algo deve:
- Propagar
trace_id(header inbound ou gerado no webhook) - Emitir
timeline_eventcomevent_typecanônico (verdata-model-v1.md) - Log estruturado em
runtime_logsem falha ou latência anômala
Toda chamada LLM (Python):
- Registrar
llm_callsantes e depois (status, tokens, latency,prompt_version) - Nunca logar prompt completo com PII em produção — usar redaction helper
12. Segurança e LGPD no código
Seção intitulada “12. Segurança e LGPD no código”| Área | Regra |
|---|---|
| PII | Helpers de redaction/masking centralizados em privacy/ |
| Secrets | Só via env/bindings — nunca commit; CI scan |
| Erasure | Use case único privacy/eraseContact — único lugar que anonimiza + dispara purge R2 |
| Consent | Checado em policy antes de outbound não-transacional |
| Logs | Sem telefone/nome/email em plaintext — hash ou truncar |
13. Naming e convenções
Seção intitulada “13. Naming e convenções”| Item | Convenção |
|---|---|
| Tabelas/colunas DB | snake_case (já no data model) |
| TS types/interfaces | PascalCase |
| TS files | kebab-case.ts |
| Python modules | snake_case.py |
| Event types | snake_case verb phrases: burst_sealed, memory_committed |
| Env vars | SCREAMING_SNAKE |
| Feature flags | tenant.settings.<flag> — não env global por tenant |
Commits: Conventional Commits — feat(orchestration):, fix(storage):, test(privacy):.
14. Anti-patterns (rejeitar em review e lint)
Seção intitulada “14. Anti-patterns (rejeitar em review e lint)”| ❌ | ✅ |
|---|---|
| SQL no orchestrator | Repo em storage/orchestration/ |
Meta API call fora de channel/actions | Port ChannelOutbound |
| LLM call direto do DO | Use case → HTTP LLM service |
Import @worker/memory/domain/... | Import @worker/memory |
| Regra de negócio no handler HTTP | Handler → application service |
environment ignorado em query de analytics | Filtro explícito prod vs simulation |
| Teste sem assert de tenant scope | Sempre 2 tenants no teste de isolamento |
any / # type: ignore sem ticket | Tipar ou ADR temporário com expiry |
15. ADRs e evolução
Seção intitulada “15. ADRs e evolução”Decisões que afetam fronteiras ou toolchain → docs/live/engineering/adrs/NNNN-título.md (template: contexto, decisão, consequências).
Mudança neste documento → PR dedicado + bump de versão no rodapé.
16. Bootstrap checklist (E0)
Seção intitulada “16. Bootstrap checklist (E0)”Referência cruzada com roadmap:
- E0.T1 — scaffold monorepo conforme §2
- E0.T2 —
pyproject.toml(uv), Ruff, ty, pytest; pnpm + Biome; Vitest -
dependency-cruiserconfig espelhando §3.1 -
import-linterconfig espelhando §3.2 - Scripts raiz:
lint,typecheck,test,boundaries,contracts:gen,quality - CI substituindo job placeholder (
ci.yml) -
.pre-commit-config.yaml - Coverage thresholds enforced (§8.2) — escopo atual (storage + LLM stubs)
- README apontando para este doc
- E0.T4 completo — repos Postgres por contexto + writer Python
llm_calls
Versão 1.0 — 2026-05-30