Commit Graph

17 Commits

Author SHA1 Message Date
159a2964cc Merge: suíte de testes de integração (agente eden-qa) 2026-09-03 11:16:13 -03:00
cdafb7f853 test(api): add Vitest+supertest integration test suite (eden-qa)
Boots the real Nest app (Test.createTestingModule + AppModule) against a
real Postgres — no mocked DB or guards anywhere in this suite. Uses SWC
(unplugin-swc) instead of Vite's default esbuild transform because
esbuild doesn't implement emitDecoratorMetadata, which silently breaks
NestJS's constructor DI (every provider resolves to undefined).

Covers:
- auth.spec.ts: anti-enumeration login message, refresh-token rotation,
  replay-detection revoking the whole session chain, idempotent logout
  (ADR-0003).
- role-weight.spec.ts: invalid role_key on invite returns 400 not 500
  (regression guard), lower-weight actor forbidden from assigning a
  heavier role even when holding users:edit (ADR-0004).
- companies.spec.ts: CNPJ digit-check validation (reject/accept).
- mass-assignment.spec.ts: forbidNonWhitelisted actually rejects
  unexpected fields on companies/products.
- reseller-assignment.spec.ts: assign-user always targets the URL's
  :id, never a body resellerId (regression guard for commit 8d108e0);
  unassign-user clears the assignment.

All fixtures are qa-test-/qa_ tagged and cleaned up (deactivated where
no DELETE endpoint exists, deleted where one does) — verified no
pre-existing seed data (super_admin, Handix Telecom LTDA, Handix revenda
própria) was touched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 11:14:43 -03:00
e7d3668154 fix: renomeia timestamp da migration de CRM (estava antes de resellers já aplicada)
node-pg-migrate recusa rodar uma migration cujo timestamp precede uma já
aplicada (check-order). O agente gerou 1788439012582 (CRM) antes de
1788439142825 (resellers) já ter sido mesclada e aplicada. Renomeado
para 1788444193993, aplicado com sucesso.
2026-09-03 11:03:39 -03:00
f56c3c9054 Integra módulo de CRM (leads/opportunities); corrige 2 bugs reais do agente
- Wire LeadsModule/OpportunitiesModule em app.module.ts; adiciona os
  exports correspondentes ao barrel @eden/database.

Bugs reais encontrados e corrigidos antes do commit (não pegos pelo
próprio agente porque ele não tinha o módulo de auth disponível no
worktree dele para testar de verdade — só stubado e revertido):

1. Os controllers de Leads/Opportunities liam `req.user?.id`, mas nosso
   JwtAuthGuard real popula `request.edenUser`, nunca `request.user` —
   todo evento de auditoria de lead/oportunidade estaria gravando
   actorId: null sempre, perdendo a atribuição de quem fez a ação.
   Trocado para o padrão real (@CurrentUser() + EdenAuthenticatedUser).
2. As chamadas appendAuditEvent() nos dois services omitiam o campo
   obrigatório `actorType` — corrigido para "user" em todas as 6
   ocorrências.

Também corrigidos: import de guards/decorators via um barrel
"../auth" inexistente (trocado pelos caminhos reais), e os dois módulos
não importavam AuthModule (necessário para o Nest resolver as
dependências do JwtAuthGuard via DI).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 11:02:40 -03:00
a8f77e4068 Add CRM leads/opportunities module (Master Prompt §6.2 MVP)
Greenfield for EDEN — eden.md has no CRM module, so this is driven by the
Master Prompt's requirements, not a legacy port.

- Migration 1788439012582_crm.cjs: leads + opportunities tables, free-text
  status/stage (documented via COMMENT ON COLUMN, no CHECK yet — a
  configurable pipeline is a future refinement), indexes on status/owner_id
  and stage/owner_id, set_updated_at triggers reusing the baseline
  schema's trigger function.
- packages/database/src/repositories/{leads,opportunities}.ts: list/get/
  create/update per companies.ts conventions (COALESCE-based partial
  update), plus convertLeadToOpportunity(leadId, input) wrapped in
  withTransaction — inserts the opportunity (lead_id set) and marks the
  lead 'converted' atomically.
- apps/api/src/{leads,opportunities}/: NestJS modules mirroring the
  Companies module structure (controller/service/module/dto). Routes
  v1/leads and v1/opportunities. Because leads/opportunities carry
  prospect PII (unlike the open-read Companies/Products catalog), reads
  are also gated behind @RequirePermission('<resource>', 'view'), not
  just JwtAuthGuard. Writes require '<resource>', 'edit'.
  POST /v1/leads/:id/convert gated by 'leads'/'edit'. Audit events:
  lead.created/updated/converted, opportunity.created/updated.
- Added class-validator/class-transformer to apps/api/package.json
  (needed for the DTOs; not previously a direct dependency).

Known cross-agent dependency gap (by design, not a bug in this code):
this worktree branched before any sibling agent's work landed, so
apps/api/src/auth/ (JwtAuthGuard, PermissionGuard, RequirePermission) and
the Companies module referenced as this task's template do not exist yet
here, and packages/database/src/index.ts / apps/api/src/app.module.ts are
explicitly off-limits for me to edit (merge-conflict avoidance across the
4 parallel agents). `pnpm --filter @eden/database typecheck/build` pass
unconditionally. `pnpm --filter @eden/api typecheck/build` currently fail
with exactly two error classes: "Cannot find module '../auth'" and
"@eden/database has no exported member <leads/opportunities repo fns,
auditRepo>" — both expected to resolve once the auth module and the
index.ts/app.module.ts wiring land at merge time. Verified locally (not
committed) by stubbing both gaps: with the stub in place, both typecheck
and build pass with zero errors, confirming this module's own code is
correct against the documented interfaces.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 10:57:28 -03:00
c9ba96f61f docs: registra progresso do lote de agentes paralelos (Users/Roles, Products, Resellers, S3/SMTP) 2026-09-03 10:18:53 -03:00
8d108e08ed fix: assign-user de reseller usava resellerId do body, não o :id da URL
Bug de design encontrado em revisão: PATCH /v1/resellers/:id/assign-user
aceitava um resellerId diferente no corpo, e a implementação usava esse
valor do corpo como alvo real da atribuição — o :id da URL virava só uma
checagem de existência, não o alvo de fato. Corrigido: o :id da URL é
agora a única fonte de verdade; desatribuição (reseller_id = null) virou
um endpoint próprio (POST /v1/resellers/unassign-user), já que 'nenhuma
revenda' não pertence à URL de nenhuma revenda específica.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 10:06:25 -03:00
1ff55afd89 Integra módulo de Resellers (agente eden-commercial)
Resolve conflitos de merge (users.ts, audit.ts, br-documents.ts,
pnpm-lock/workspace.yaml — o worktree partiu de uma base anterior à
existência do módulo de auth/companies, então recriou versões próprias
de arquivos que já existiam no main; mantido o conteúdo do main,
incorporando só a função nova assignUserToReseller). Adiciona
resellersRepo ao barrel @eden/database e ResellersModule ao app.module.ts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 09:54:33 -03:00
eeb6977e36 fix: assertCanAssignRole/assertCanActOnUser validam existência do papel mesmo com bypass do super_admin
Bug encontrado em teste manual: convidar usuário com role_key inexistente,
sendo o ator super_admin, pulava a checagem de peso inteira (incluindo a
validação de existência) e caía direto na constraint FK do banco, virando
um 500 não tratado em vez de um 400 claro. Agora a existência do papel é
sempre validada antes do bypass de peso.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 09:52:07 -03:00
dca6eb2766 Add operational Resellers module (MVP CRUD, eden.md §1.7/§7)
Migration 1788439142825_resellers.cjs: `resellers` table plus nullable
`users.reseller_id` (scope=reseller per ADR-0004), indexes on
resellers.active and users.reseller_id.

packages/database/src/repositories/resellers.ts: list/get/create/update,
COALESCE-based partial update, mirroring the companies repository
pattern. Also adds repositories/audit.ts (hash-chained appendAuditEvent
over the existing audit_log table) and repositories/users.ts
(assignUserToReseller) - foundational pieces this module depends on that
hadn't landed in this worktree yet; audit.ts mirrors the sibling
worktree's already-built implementation verbatim to minimize merge
friction, and users.ts intentionally contains only the one function the
task asked to append (the fuller users repository belongs to the
auth/identity module being built in parallel).

apps/api/src/resellers/*: controller/service/module/dto mirroring the
Companies module's conventions - read open to any authenticated user,
write gated by @RequirePermission('resellers', 'edit'). Deliberate
deviation from legacy noted in code comments: the legacy hardcoded this
CRUD to requireAdmin and exposed reseller<->user assignment as
`PATCH /users/:id`; EDEN generalizes to the permission model (ADR-0004)
and adds `PATCH /v1/resellers/:id/assign-user` instead. Audits
reseller.created / reseller.updated / reseller.user_assigned.

packages/domain-shared/src/validators/br-documents.ts: CPF/CNPJ
digit-check validators (mirrors the sibling worktree's implementation
verbatim), used for the reseller `document` field the same way Companies
validates cnpj.

Known integration gap (by design, not a bug in this module): this
worktree doesn't yet have apps/api/src/auth/* or the companies-phase
exports in packages/database/src/index.ts - both are being built in
sibling worktrees and are explicitly out of scope here (per the task's
boundaries, app.module.ts and packages/database/src/index.ts are left
untouched for the user to wire up at merge time). @eden/database
typecheck/build pass cleanly; @eden/api typecheck/build currently fail
with exactly ten "cannot find module ../auth/..." /
"no exported member resellersRepo/usersRepo/auditRepo/Reseller" errors,
all attributable to those two integration points and nothing else.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 09:44:45 -03:00
d94885a681 Integra Products e adapters S3/SMTP; corrige desvios pós-merge dos agentes
- Wire ProductsModule em app.module.ts; troca o import "deep path" que o
  agente usou (packages/database/dist/...) pela exportação correta via
  barrel (@eden/database), já que packages/database/src/index.ts é meu
  para editar durante a integração.
- S3/SMTP: troca leitura direta de process.env por loadEnv()/@eden/config
  (que não existia ainda quando o agente rodou no worktree dele).
- Resolve conflito de pnpm-lock.yaml (regenerado via pnpm install).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 09:44:25 -03:00
676806e1eb Merge: Products/Catalog module (agente eden-commercial) 2026-09-03 09:40:44 -03:00
8f638f2743 CRUD de Usuários e Papéis (role weight, permissões, gitignore de worktrees)
- apps/api/src/users/: invite (senha temporária, mesma entropia do
  legado), PATCH (role/isActive, com checagem de peso), reset de senha
  administrativo, troca de senha self-service, grant/revoke de acesso a
  aplicação — cada ação sensível auditada.
- apps/api/src/roles/: CRUD de papéis e gestão de role_permissions,
  exclusivo super_admin (SuperAdminGuard), mirando o requireSuperAdmin do
  legado — deliberadamente não generalizado para o modelo de permissão
  resource+action+scope, por ser a raiz do próprio sistema de
  autorização (documentado em roles.service.ts).
- apps/api/src/auth/role-weight.ts: assertCanAssignRole/assertCanActOnUser
  (eden.md §1.6) — um ator nunca atribui papel nem mexe em usuário com
  peso maior que o seu; super_admin sempre passa.
- .gitignore: .claude/worktrees/ (worktrees temporários de subagentes
  paralelos nunca devem virar submódulo do repo principal).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 09:40:28 -03:00
95090ba24b feat(integrations): add S3 and SMTP adapters
Port the legacy server/src/lib/s3.js and mailer.js (eden.md §4/§5) into
packages/integrations behind an adapter/port pattern, isolated from
domain logic per Master Prompt §13.

- s3/client.ts: @aws-sdk/client-s3 wrapper (uploadObject, getObjectStream,
  deleteObject, getClient/getBucket for future multipart Backup uploads).
  Lazily-initialized singleton client, forcePathStyle from
  S3_FORCE_PATH_STYLE (default true). Throws a clear "S3 não configurado
  (S3_ENDPOINT ausente)" whenever S3_ENDPOINT is unset — storage failures
  must be loud, never a silent no-op.

- smtp/mailer.ts: nodemailer wrapper (sendMail). Lazily-initialized
  singleton transporter, secure:true auto-detected on port 465, from
  defaults to SMTP_FROM or SMTP_USER. Deliberately fail-soft: if
  SMTP_HOST is unset, or if sending fails, logs and returns normally —
  no business flow ever fails because of email, matching the legacy's
  behavior exactly.

@eden/config's loadEnv()/env-schema is still a placeholder in this
worktree, so both adapters read process.env directly using the exact
var names already reserved in the target schema; @eden/config is added
as a workspace dependency so this can be swapped for a real loadEnv()
call later without touching callers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 09:38:41 -03:00
e5fd75c44a Add Products/Catalog module (MVP): migration, repository, API
Ports the legacy products table's core shape (eden.md §2 §1.1, Master
Prompt §6.3) faithfully but MVP-scoped: product_code sequence, the 5 fixed
price tiers (price_0/12/24/36/48), impl_unit_price, and stock/serial/
asset-tag/MAC tracking flags. Leaves out legacy telecom-billing specifics
(service_type, metered, tariff_rates, ixc_product_code) for a later phase.

Mirrors the Companies module's structure and conventions exactly: COALESCE
partial updates, JwtAuthGuard/PermissionGuard with open read / gated write
(products:edit) per ADR-0004, product.created/product.updated audit events.
Money fields are validated as non-negative NUMERIC in the DTO only — no
pricing/calculation logic here, that belongs to the future Quotes phase.

Does not touch apps/api/src/app.module.ts or packages/database/src/index.ts
(wiring is left to integration) to avoid conflicting with sibling agents'
concurrent edits to those files; the ProductsService deep-imports the
repository module directly instead of via the shared barrel, noted in a
code comment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 09:38:35 -03:00
8d81cf506b Auth completo (Argon2id + JWT/refresh rotativo), permissões, Companies e OpenAPI
- Auth (apps/api/src/auth/): Argon2id com parâmetros versionados
  (ADR-0005), access token JWT de 15min, refresh token opaco de 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 /api/v1/auth/
  {login,refresh,logout,me}.
- Permissões resource+action+scope (ADR-0004) via PermissionGuard,
  resolvidas do banco a cada request (nunca cacheadas no JWT) — mudança
  de papel tem efeito imediato. super_admin com bypass hardcoded,
  espelhando a trava já existente no banco.
- Companies (migration + CRUD): leitura aberta a qualquer autenticado,
  escrita restrita a companies:manage; validação de CNPJ via
  packages/domain-shared (dígito verificador).
- SeedService: cria o primeiro super_admin a partir de
  EDEN_SUPERADMIN_EMAIL/PASSWORD só quando `users` está vazia.
- OpenAPI/Swagger em /api/docs; ValidationPipe global com
  whitelist+forbidNonWhitelisted (proteção contra mass-assignment).
- packages/database: repositórios (roles/users/sessions/audit/companies)
  reutilizáveis por api e worker.

Validado manualmente contra a stack real (não só typecheck): login com
senha errada → 401 genérico; refresh rotaciona e invalida o token
anterior; reuso do token antigo → replay detectado, cadeia revogada;
logout idempotente; audit log com a sequência completa de eventos;
CNPJ inválido rejeitado; mass-assignment rejeitado.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 09:25:48 -03:00
44510bd019 Bootstrap EDEN: Fase 0 (arquitetura) e Fase 1 (monorepo + infra)
Fase 0 — descoberta e arquitetura:
- Inventário do projeto, glossário de domínio, arquitetura com bounded
  contexts e topologia de containers, threat model inicial.
- 12 ADRs cobrindo modular monolith, topologia de containers (Postgres
  isolado + eden-core/parceiros/assinante em containers e portas
  distintos), auth/sessões, modelo de permissões, criptografia/segredos,
  contrato first-class, stock ledger, separação billing/finance/fiscal,
  outbox transacional, adapters SaperX e Focus NFe, e identidade
  compartilhada entre as 3 apps.
- 14 subagentes e 7 skills especializados por domínio em .claude/.
- Hooks de segurança (PreToolUse/PostToolUse/Stop) testados via pipe.

Fase 1 — plataforma (em andamento):
- Monorepo pnpm workspaces + Turborepo: apps/{api,worker,core-web,
  reseller-web,subscriber-web} + 9 packages compartilhados.
- apps/api: NestJS mínimo com /health/live e /health/ready (checando
  Postgres real via @eden/database).
- 3 frontends Vite + React + TypeScript + Tailwind, com o favicon
  oficial do EDEN.
- packages/database: migration baseline (node-pg-migrate) criando
  roles/role_permissions/applications/users/user_applications/sessions/
  audit_log — audit log append-only com hash-chain, testado ao vivo
  (UPDATE/DELETE bloqueados pelo trigger).
- compose.yaml implementando a topologia da ADR-0002, validada de ponta
  a ponta: os 6 containers sobem e ficam saudáveis com um único
  `docker compose up`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 08:01:14 -03:00