Fecha o núcleo comercial da Fase 2/3: motor de precificação de Ofertas (feito diretamente, dado o risco de erro de arredondamento/regra de negócio), Cadastro de Cliente e esqueleto de Contratos (via 2 subagentes em paralelo, salvos manualmente após ambos baterem no limite de gasto mensal da conta — trabalho não commitado nos worktrees foi revisado arquivo a arquivo e copiado para main, com as peças faltantes completadas diretamente). - Quotes: pricing.ts porta as fórmulas do legado (eden.md §7/§8) — needsApproval, faixas de condição de pagamento, efeito legal da fidelidade condicionado a approval_status==='approved'. Fluxo criar→decidir aprovação→fechar negócio (vinculado a um cadastro de cliente, com guarda contra fechamento duplo). - Client Registration: cadastro direto (eden.md §3), MVP autenticado-only (sem token público de autoatendimento, upload ou lookup CNPJ/CEP nesta fase — documentado em comentário). - Contracts: máquina de estados do ADR-0006 imposta via SELECT...FOR UPDATE + tabela de transições, com signed_at/activated_at e histórico de status; 4 das 11 tabelas do ADR-0006 (as demais adiadas). 2 bugs próprios corrigidos antes do commit: UpdateQuoteInput usava COALESCE para campos financeiros (undefined do JS apagaria valor em update parcial) — campos tornados obrigatórios; User.reseller_id/ can_approve_discount faltavam em types.ts apesar das colunas já existirem no banco desde migrations anteriores. Verificado: pnpm turbo run typecheck build (28/28), pnpm test (15/15), e todo o fluxo testado ao vivo via curl contra a stack Docker real, incluindo cenários de erro (transição de status inválida, FK de produto inexistente, fechamento duplo, 401 sem token) e trilha de auditoria conferida no Postgres. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
21 KiB
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 comSKILL.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)
- Política de retenção/eliminação de dados pessoais (LGPD) — Handix ainda não definiu.
- Docker/Node/pnpm precisam ser provisionados no ambiente antes da Fase 1.
- Portas dos containers definidas via
.env(não fixas) — ver ADR-0002. - Git ainda não iniciado no projeto — planejado para o início da Fase 1.
- Inventário fino do
tema_do_Eden.zip(extração de componentes) adiado para o início deeden-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.iodo 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), remotoorigin=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 compackage.json/tsconfig.jsonválidos. pnpm install,pnpm turbo run typecheckepnpm turbo run buildpassam 100% (15/15 e 14/14 tarefas, respectivamente) em todo o monorepo.apps/api: NestJS mínimo com/health/livee/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 vianode-pg-migrate(formato.cjs), client de query (pgPool).
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/DELETEsão efetivamente bloqueados pelo trigger, só liberados viaSET 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):
- Imagem oficial
postgres:18-alpinemudou o layout do volume — precisa montar/var/lib/postgresql(não.../data). apps/api/package.jsontinha"type": "module"incompatível com otsconfig(saída CommonJS) — Node falhava comexports is not defined.node-pg-migrate --envPathnão carregava o.envcorretamente nesta versão — trocado porbash -c 'source .env; ...'nos scriptsmigrate:up/migrate:down.- Healthcheck dos 3 frontends usava
wget http://localhost/..., que resolve para::1(IPv6) dentro do container — nginx só escuta IPv4; trocado para127.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). EndpointsPOST /api/v1/auth/{login,refresh,logout}eGET /api/v1/auth/me. - Permissões (
apps/api/src/auth/permissions.ts,permission.guard.ts): resource+action+scope (ADR-0004),super_admincom bypass hardcoded (nunca consultarole_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/+ migration1788437478189_companies): CRUD com leitura aberta a qualquer autenticado e escrita restrita acompanies:manage; validação de CNPJ (dígito verificador,packages/domain-shared) testada com CNPJ válido/inválido conhecidos. - Seed automático:
SeedServicecria o primeirosuper_admina partir deEDEN_SUPERADMIN_EMAIL/PASSWORDsó quandousersestá vazia — testado (logsuper_admin inicial criado). - OpenAPI/Swagger:
/api/docs(UI) e/api/docs-json, cobrindo todos os endpoints atuais. - Validação/segurança de request:
ValidationPipeglobal comwhitelist+forbidNonWhitelisted(proteção contra mass-assignment) — testado (id/isActiveextras 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 erole_permissionsexclusivosuper_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/+ migrationproducts): catálogo com as 5 faixas de preço fixas,product_codesequencial, flags de rastreamento (estoque/serial/patrimônio/MAC). Leitura aberta, escrita viaproducts:edit. -
Resellers (
apps/api/src/resellers/+ migrationresellers): CRUD operacional + vínculo usuário↔revenda (users.reseller_id). Leitura aberta, escrita viaresellers:edit— desvio deliberado do legado (que travava emrequireAdmin), 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/+ migrationcrm): 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:
assertCanAssignRole/assertCanActOnUserpulavam a validação de existência do papel quando o ator erasuper_admin(o bypass de peso pulava a checagem inteira) — convidar usuário comrole_keyinexistente estourava a constraint FK do banco como 500 não tratado em vez de 400 claro.PATCH /v1/resellers/:id/assign-useraceitava umresellerIddiferente no corpo, que na prática era o valor usado — o:idda URL virava só uma checagem de existência, não o alvo real da atribuição. Corrigido para o:idda URL ser a única fonte de verdade; desatribuição virou endpoint próprio (POST /v1/resellers/unassign-user).- Os controllers de Leads/Opportunities liam
req.user?.idem vez de usar@CurrentUser()/request.edenUser(o padrão real doJwtAuthGuard) — todo evento de auditoria de lead/oportunidade estaria gravandoactorId: nullsempre. 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). - As chamadas
appendAuditEvent()de leads/opportunities omitiam o campo obrigatórioactorType(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, lead→oportunidade transacional.
Suíte de testes de integração (agente eden-qa)
Vitest + supertest + @nestjs/testing (bootstrap real do Nest, sem mock) contra o Postgres real — 5 arquivos, 15 testes, todos verdes: 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), companies.spec.ts (CNPJ), mass-assignment.spec.ts (whitelist), reseller-assignment.spec.ts (guarda a regressão do bug #2). Achado técnico do agente: o esbuild (transform padrão do Vitest) não implementa emitDecoratorMetadata, quebrando toda injeção de dependência do Nest silenciosamente sob teste — corrigido trocando para SWC (unplugin-swc), abordagem padrão documentada pelo próprio NestJS. Rodar com pnpm --filter @eden/api test.
Resumo do lote de agentes paralelos
6 subagentes no total (4 + 1 QA + Users/Roles feito diretamente), rodando em git worktree isolados, revisados e mesclados manualmente um a um (nunca merge automático sem verificação). 4 bugs reais encontrados só porque cada módulo foi testado contra a stack viva antes do commit — nenhum deles teria aparecido só com typecheck/build, que todos os agentes já reportavam verde. Padrão validado: delegar implementação para agentes especializados funciona bem para módulos CRUD bem especificados, mas a integração final (merge, wiring, teste ao vivo, revisão) precisa ficar com quem tem visão do sistema inteiro.
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/uiainda é 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.
Fase 2/3 — Ofertas (Quotes), Cadastro de Cliente (Customer 360) e esqueleto de Contratos
Data: 2026-09-03.
Ofertas / Quotes (apps/api/src/quotes/ + migration quotes) — implementado diretamente
Núcleo comercial mais sensível a erro de arredondamento/regra de negócio (eden.md §7/§8), por isso feito sem delegar a subagente:
pricing.ts: funções puras porteadas literalmente do legado —monthlyTotal = Σitem.total;totalImpl = implFromProducts + implGeral;hasSpecialPrice/isDiscount/isMarkuppor comparação comproposedMonthlyTotal;contractTotal = effectiveMonthly * (contractPeriod||1) + totalImpl;needsApproval = isDiscount || hasReducedFidelity; fidelidade só tem efeito legal comapproval_status === 'approved'. Faixas de condição de pagamento (À vista≤1500;+1+1≤3000;+1+2acima) replicadas do legado.- Preço sempre derivado server-side do produto vivo no banco (nunca confia em preço vindo do cliente).
- Fluxo completo: criar oferta → decidir aprovação (
PATCH :id/approval) → fechar negócio vinculando a um cadastro de cliente (POST :id/close, bloqueado porclient_registration_id IS NULL— não fecha duas vezes).
Customer 360 / Cadastro de Cliente (apps/api/src/client-registrations/ + migration client-registrations) e Contratos (apps/api/src/contracts/ + migration contracts) — subagentes + salvamento manual
Lançados 2 subagentes em paralelo (worktrees isolados) para Cadastro de Cliente e esqueleto de Contratos. Ambos falharam duas vezes: primeiro com rate limit de sessão (retomado após o usuário habilitar cobrança de excedente), depois com limite de gasto mensal da conta — um bloqueio de nível de conta que não se resolve tentando de novo. Em vez de insistir, os dois worktrees foram inspecionados (git status, diff arquivo a arquivo); o trabalho não commitado encontrado em ambos era substancial e de boa qualidade — foi copiado diretamente para main (sem passar por branch/merge, já que nada tinha sido commitado) e o que faltava foi completado manualmente:
- Cadastro de Cliente: repositórios e camada de API salvos do agente praticamente inalterados; só faltava o
client-registrations.module.ts, escrito manualmente. MVP com escopo explícito (eden.md §3): só fluxo autenticado, sem token de autoatendimento público, sem upload de anexo, sem integração CNPJ/CEP — tudo documentado em comentário no código, não descartado silenciosamente. - Contratos: repositório salvo do agente (com um import não usado removido); toda a camada de API (DTOs, service, controller, module) escrita manualmente, pois o agente não tinha chegado lá. Máquina de estados (ADR-0006)
draft→{pending_signature,cancelled},pending_signature→{active,cancelled},active→{suspended,cancelled,terminated,expired},suspended→{active,cancelled,terminated}— imposta viaSELECT...FOR UPDATE+ tabela de transições, comsigned_at/activated_atcarimbados na transição correta e histórico completo emcontract_status_history. Escopo: 4 das 11 tabelas do ADR-0006 (contracts, contract_items, contract_parties, contract_status_history); as outras 7 explicitamente adiadas.
Wiring final feito manualmente: barrel (packages/database/src/index.ts) e app.module.ts atualizados; 3 migrations aplicadas ao Postgres real; pnpm turbo run typecheck build (28/28 tasks) e pnpm --filter @eden/api test (15/15 testes) verdes; container eden-api reconstruído e todo o fluxo testado ao vivo via curl contra a stack real:
- Oferta sem preço especial (sem aprovação) e oferta com desconto + fidelidade reduzida (
approval_status: pending) — valores conferidos manualmente contra a fórmula. - Aprovação da oferta, fechamento vinculando a um cadastro de cliente criado via
POST /client-registrations/direct, e guarda de fechamento duplo (409 na segunda tentativa). - Contrato criado a partir da oferta fechada, transições de status válidas e inválidas (draft→active direto rejeitado com 409; draft→pending_signature→active aceito,
signed_at/activated_atcarimbados corretamente), adição de item/parte, FK de produto inexistente rejeitada com 400. - 401 sem token; trilha de auditoria (
audit_log) conferida no Postgres com a sequência completa de eventos do lote (quote.created,quote.approval_approved,quote.closed,client_registration.created,contract.created,contract.status_changed×2,contract.party_added,client_registration.updated).
2 bugs reais encontrados e corrigidos antes do commit (nenhum via subagente — achados na própria implementação/revisão):
- Primeiro rascunho do
updateQuote(escrito por mim) usavaCOALESCEpara os campos financeiros (fidelity_period,proposed_monthly_total,approved_by_id) — comoundefineddo JS também viraNULLno SQL, qualquer update parcial que omitisse esses campos os apagaria silenciosamente. Corrigido tornando esses campos obrigatórios emUpdateQuoteInput(não mais opcionais), forçando a camada de serviço a sempre fornecer um valor deliberado. packages/database/src/repositories/types.ts— a interfaceUserestava semreseller_id/can_approve_discount, mesmo essas colunas já existindo no banco desde as migrations de Resellers/Quotes (ninguém tinha atualizado o tipo compartilhado). Descoberto ao ligar o uso desses campos em Quotes; corrigido notypes.tse propagado paraEdenAuthenticatedUseremjwt-auth.guard.ts.
Pendências deste lote
- FKs cruzadas entre as 3 tabelas ainda não existem (ficaram como UUID nullable simples, documentado em comentário nas migrations):
quotes.client_registration_id → client_registrations(id),client_registrations.quote_id → quotes(id),contracts.quote_id → quotes(id),contracts.client_registration_id → client_registrations(id). - Fluxo de autoatendimento público (token) de Cadastro de Cliente, upload de anexo, integração CNPJ/CEP — todos fora do escopo desta MVP.
- 7 das 11 tabelas do ADR-0006 de Contratos (aditivos, faturas, documentos/e-signature, etc.) ainda não criadas.