Files
eden/docs/progress.md
Matheus (Handix) 860c21df07 Integra suíte de testes de integração (agente eden-qa)
Vitest + supertest + NestJS TestingModule contra o Postgres real (não
mockado, conforme Master Prompt §15.1). Bootstrap via SWC (não esbuild
puro) porque o esbuild não implementa emitDecoratorMetadata — sem isso,
toda injeção de dependência via decorator do Nest quebra silenciosamente
sob Vitest (achado real do agente, não bug de produto).

5 arquivos de teste, 15 casos, todos verdes contra a stack real:
- auth.spec.ts: anti-enumeração, rotação de refresh, detecção de replay
  fim-a-fim, logout idempotente.
- role-weight.spec.ts: guarda a regressão do bug #1 (papel inexistente
  → 400) e o peso hierárquico (ator fraco não atribui papel mais forte).
- companies.spec.ts: CNPJ inválido rejeitado, CNPJ válido aceito.
- mass-assignment.spec.ts: whitelist rejeita campo inesperado.
- reseller-assignment.spec.ts: guarda a regressão do bug #2 (resellerId
  do corpo não é mais aceito; :id da URL é a única fonte de verdade).

Cada fixture criada pela suíte é prefixada (qa-test-/qa_) e limpa em
afterAll (desativada onde não há endpoint DELETE, removida onde há) —
confirmado que nenhum dado pré-existente (super_admin seedado, Handix
Telecom LTDA, etc.) foi alterado.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 11:17:24 -03:00

14 KiB
Raw Blame History

EDEN — Progresso

Fase 0 — Descoberta e Arquitetura: CONCLUÍDA

Data: 2026-09-03.

Entregáveis

  • docs/project-inventory.md, docs/glossary.md, docs/architecture.md, docs/assumptions.md, docs/security/threat-model.md, docs/implementation-plan.md, docs/progress.md (este arquivo).
  • 12 ADRs em docs/adr/ (0001–0012), cobrindo: modular monolith, topologia de containers (Postgres isolado + eden-core/parceiros/assinante em containers/portas distintas — decisão explícita do operador), auth/sessões, modelo de permissões, criptografia/segredos, contrato first-class, stock ledger, separação billing/finance/fiscal, outbox transacional, adapter SaperX, adapter Focus NFe, três apps com identidade compartilhada.
  • 14 subagentes em .claude/agents/: eden-architect, eden-database, eden-security, eden-commercial, eden-finance, eden-fiscal, eden-inventory, eden-telecom, eden-support, eden-hr-timeclock, eden-frontend, eden-api-integrations, eden-qa, eden-code-reviewer.
  • 7 skills em .claude/skills/: eden-domain, eden-security, eden-finance, eden-fiscal, eden-inventory, eden-telecom, eden-timeclock — cada uma com SKILL.md + references/.
  • Hooks em .claude/settings.json + .claude/hooks/*: PreToolUse (guarda de comandos Bash destrutivos/externos e de acesso a arquivo fora do projeto/.ssh//etc/segredo), PostToolUse (lint/typecheck incremental, fail-open enquanto pnpm não existir), Stop (checagem advisória de segredo/TODO crítico no diff, silenciosa até existir repositório git). Testados via pipe com payloads sintéticos — todos os cenários (allow/ask/deny/fail-open) se comportaram como esperado.

Revisão cruzada (self-review contra os critérios dos agentes eden-architect / eden-security / eden-database, criados nesta mesma fase)

eden-architect: bounded contexts (docs/architecture.md §2) têm direção de dependência consistente (nenhum ciclo); HR/Timeclock corretamente isolado; candidatos a extração futura (Billing, Fiscal) identificados sem extração prematura. Nenhuma feature de negócio foi implementada antes deste gate — consistente com o Master Prompt §18.

eden-security: threat model cobre as superfícies do legado (rotas públicas, auth, autorização, integrações, upload, PDF, auditoria, infraestrutura) mais os invariantes de teste obrigatórios (§15.3). Hooks PreToolUse já impõem, a nível de ferramenta, várias regras de §2.1/§2.2 (fora do projeto, .ssh//etc, impressão de .env, SSH/SCP externo, remoção ampla de volume/prune). Pendência real registrada (não bloqueante): política de retenção/eliminação LGPD ainda não definida pela Handix (docs/assumptions.md #1).

eden-database: princípios de modelagem (§11) já refletidos nas ADRs de contrato/estoque/billing (nunca saldo editável, nunca CASCADE indiscriminado, JSONB só para snapshot/metadata). Nenhum schema físico foi criado ainda nesta fase — corretamente adiado para a Fase 1, quando o Postgres 18 realmente subir.

Nota: esta revisão foi feita pela própria sessão orquestradora aplicando os critérios definidos nos arquivos de agente recém-criados. Os subagentes dedicados (eden-architect, eden-security, eden-database) ficam disponíveis para revisões reais em sessões futuras assim que o Claude Code os carregar como subagent types.

Pendências não bloqueantes (ver docs/assumptions.md)

  1. Política de retenção/eliminação de dados pessoais (LGPD) — Handix ainda não definiu.
  2. Docker/Node/pnpm precisam ser provisionados no ambiente antes da Fase 1.
  3. Portas dos containers definidas via .env (não fixas) — ver ADR-0002.
  4. Git ainda não iniciado no projeto — planejado para o início da Fase 1.
  5. Inventário fino do tema_do_Eden.zip (extração de componentes) adiado para o início de eden-frontend.

Gate de saída da Fase 0: VERDE

Todos os itens do checklist da seção 22 do Master Prompt (“Primeira Execução”) estão completos.


Fase 1 — Plataforma: EM ANDAMENTO

Data de início: 2026-09-03.

Ambiente provisionado

  • Docker CE 29.7.2 + Compose v5.5.0 (repositório oficial Docker, não o docker.io do Debian) instalados via apt.
  • Node.js 22.23.2 LTS (NodeSource) + pnpm 11.25.0 (via corepack).
  • Git 2.47.3. Repositório inicializado (main), remoto origin = https://git.falehandix.com.br/Matheus/eden.git (nenhum push feito ainda).

Monorepo

  • pnpm workspaces + Turborepo. 5 apps (api, worker, core-web, reseller-web, subscriber-web) + 9 packages, todos com package.json/tsconfig.json válidos.
  • pnpm install, pnpm turbo run typecheck e pnpm turbo run build passam 100% (15/15 e 14/14 tarefas, respectivamente) em todo o monorepo.
  • apps/api: NestJS mínimo com /health/live e /health/ready (este último checando Postgres de verdade via @eden/database).
  • apps/worker: placeholder que confirma conectividade com o banco; sem BullMQ/Redis ainda (conforme ADR-0002 — só entra quando houver job real).
  • apps/core-web, apps/reseller-web, apps/subscriber-web: Vite + React + TS + Tailwind, cada um com favicon oficial do EDEN.
  • packages/database: migrations via node-pg-migrate (formato .cjs), client de query (pg Pool).

Banco de dados

  • Migration baseline aplicada com sucesso contra Postgres 18.6 real (container eden-postgres): roles, role_permissions, applications, users, user_applications, sessions, audit_log — cobrindo Identity & Access (ADR-0003/ADR-0004) e um audit log append-only com hash-chain (testado: UPDATE/DELETE são efetivamente bloqueados pelo trigger, só liberados via SET LOCAL eden.allow_audit_mutation).
  • Seeds: 4 papéis do sistema (pesos idênticos ao legado) + as 3 aplicações (core/reseller/subscriber).
  • Organization (empresas/entidades legais), Commercial e demais bounded contexts ainda não têm migration — ficam para quando cada fase começar, por design (ver ADR-0001).

Docker Compose — topologia ADR-0002 validada de ponta a ponta

docker compose --env-file .env up -d sobe os 6 serviços com sucesso:

Serviço Status observado
eden-postgres healthy — porta dev 55432, nunca 5432 direto
eden-api healthy — único serviço com DATABASE_URL; /health/ready confirma Postgres via rede interna
eden-worker up — conecta ao Postgres, sem porta exposta
eden-core (:3001) healthy — HTTP 200
eden-parceiros (:3002) healthy — HTTP 200
eden-assinante (:3003) healthy — HTTP 200

Bugs de bootstrap corrigidos nesta sessão (documentados aqui para não se repetirem):

  1. Imagem oficial postgres:18-alpine mudou o layout do volume — precisa montar /var/lib/postgresql (não .../data).
  2. apps/api/package.json tinha "type": "module" incompatível com o tsconfig (saída CommonJS) — Node falhava com exports is not defined.
  3. node-pg-migrate --envPath não carregava o .env corretamente nesta versão — trocado por bash -c 'source .env; ...' nos scripts migrate:up/migrate:down.
  4. Healthcheck dos 3 frontends usava wget http://localhost/..., que resolve para ::1 (IPv6) dentro do container — nginx só escuta IPv4; trocado para 127.0.0.1.

Auth, permissões, Companies e OpenAPI — implementados e testados ao vivo

  • Auth (apps/api/src/auth/): Argon2id (parâmetros versionados, ADR-0005), JWT de acesso de 15 min, refresh token opaco (256 bits) com rotação e detecção de replay (reuso de token já rotacionado revoga toda a cadeia de sessões do usuário). Endpoints POST /api/v1/auth/{login,refresh,logout} e GET /api/v1/auth/me.
  • Permissões (apps/api/src/auth/permissions.ts, permission.guard.ts): resource+action+scope (ADR-0004), super_admin com bypass hardcoded (nunca consulta role_permissions, espelhando a trava também existente no banco). Guard resolve permissões do banco a cada request (nunca cacheado no JWT) — mudança de papel tem efeito imediato, mesma filosofia do legado.
  • Companies (apps/api/src/companies/ + migration 1788437478189_companies): CRUD com leitura aberta a qualquer autenticado e escrita restrita a companies:manage; validação de CNPJ (dígito verificador, packages/domain-shared) testada com CNPJ válido/inválido conhecidos.
  • Seed automático: SeedService cria o primeiro super_admin a partir de EDEN_SUPERADMIN_EMAIL/PASSWORD só quando users está vazia — testado (log super_admin inicial criado).
  • OpenAPI/Swagger: /api/docs (UI) e /api/docs-json, cobrindo todos os endpoints atuais.
  • Validação/segurança de request: ValidationPipe global com whitelist+forbidNonWhitelisted (proteção contra mass-assignment) — testado (id/isActive extras num POST de company são rejeitados com 400).

Testes manuais realizados contra a stack real (não só typecheck): login com senha errada → 401 genérico; refresh rotaciona e o token antigo some; reuso do token antigo → 401 + toda a cadeia revogada; logout revoga e é idempotente; audit log com a sequência completa de eventos (auth.login, auth.refresh, auth.refresh_replay_detected, company.created).

CRUD de Usuários/Papéis, Products, Resellers, adapters S3/SMTP — implementados via 4 subagentes especializados em paralelo (worktrees isolados) + trabalho direto

Estratégia: 4 subagentes (general-purpose, atuando com a persona eden-commercial/eden-api-integrations definida em .claude/agents/, já que os subagent types customizados ainda não carregaram nesta sessão) rodaram em paralelo, cada um em git worktree isolado, enquanto o CRUD de Usuários/Papéis foi feito diretamente (por tocar peso hierárquico e segurança). Cada agente foi instruído a não editar app.module.ts nem packages/database/src/index.ts — a integração final (merge das branches, wiring, resolução de conflitos, rebuild, teste ao vivo) foi feita manualmente.

  • Users/Roles (apps/api/src/users/, apps/api/src/roles/): convite com senha temporária, troca de senha self-service, reset administrativo, grant/revoke de aplicação — tudo com checagem de peso hierárquico (assertCanAssignRole/assertCanActOnUser, eden.md §1.6). CRUD de papéis e role_permissions exclusivo super_admin (SuperAdminGuard), deliberadamente não generalizado para o modelo de permissão (raiz do próprio sistema de autorização).

  • Products (apps/api/src/products/ + migration products): catálogo com as 5 faixas de preço fixas, product_code sequencial, flags de rastreamento (estoque/serial/patrimônio/MAC). Leitura aberta, escrita via products:edit.

  • Resellers (apps/api/src/resellers/ + migration resellers): CRUD operacional + vínculo usuário↔revenda (users.reseller_id). Leitura aberta, escrita via resellers:edit — desvio deliberado do legado (que travava em requireAdmin), documentado em comentário.

  • Adapters S3/SMTP (packages/integrations/): portados fielmente do legado — S3 falha alto (erro claro se não configurado), SMTP falha silencioso (nunca quebra um fluxo de negócio por causa de e-mail).

  • CRM (apps/api/src/leads/, apps/api/src/opportunities/ + migration crm): leads e oportunidades com conversão transacional (convertLeadToOpportunity, FOR UPDATE + withTransaction). Diferente de Companies/Products, leitura também exige permissão (leads:view/opportunities:view) por conter PII de prospect.

4 bugs reais encontrados em teste manual e corrigidos antes do commit final:

  1. assertCanAssignRole/assertCanActOnUser pulavam a validação de existência do papel quando o ator era super_admin (o bypass de peso pulava a checagem inteira) — convidar usuário com role_key inexistente estourava a constraint FK do banco como 500 não tratado em vez de 400 claro.
  2. PATCH /v1/resellers/:id/assign-user aceitava um resellerId diferente no corpo, que na prática era o valor usado — o :id da URL virava só uma checagem de existência, não o alvo real da atribuição. Corrigido para o :id da URL ser a única fonte de verdade; desatribuição virou endpoint próprio (POST /v1/resellers/unassign-user).
  3. Os controllers de Leads/Opportunities liam req.user?.id em vez de usar @CurrentUser()/request.edenUser (o padrão real do JwtAuthGuard) — todo evento de auditoria de lead/oportunidade estaria gravando actorId: null sempre. O agente que os escreveu não tinha o módulo de auth disponível no worktree dele para testar isso de verdade (só stubou e reverteu antes de commitar).
  4. As chamadas appendAuditEvent() de leads/opportunities omitiam o campo obrigatório actorType (6 ocorrências).

Também precisou renomear o timestamp da migration de CRM, que o agente gerou com valor anterior ao da migration de resellers (já aplicada por outro agente/merge) — node-pg-migrate recusa migration fora de ordem.

Todos os módulos testados ao vivo contra a stack real (não só typecheck): convite de usuário, papel inexistente (400 após o fix), CRUD de produto com preços NUMERIC corretos, CRUD de revenda, atribuição/desatribuição de usuário à revenda.

Pendências da Fase 1 (não iniciadas)

  • MFA/TOTP (schema já existe — users.mfa_* — endpoint ainda não implementado).
  • Extração do Design System a partir do tema DreamsERP (packages/ui ainda é placeholder).
  • Wiring dos adapters S3/SMTP em algum módulo consumidor real (hoje só existem como biblioteca em packages/integrations, nada os chama ainda).
  • Nota de dado de teste: a empresa "Handix Telecom LTDA" e a revenda "Handix (revenda própria)" criadas durante os testes usam dados de exemplo, não os dados reais da Handix — substituir/remover antes de qualquer ambiente que não seja dev local.