feat(api,ui): MFA/TOTP, Inventory, Support, HR-Timeclock, Fiscal, Billing e Design System
Leva grande, pedida como "pelo menos mais 20 etapas, use subagentes":
fecha a pendência de MFA/TOTP da Fase 1 (feito diretamente, autenticação é
sensível demais para delegar) e lança 5 subagentes em paralelo em 2 ondas
(eden-inventory, eden-support, eden-hr-timeclock; depois eden-finance,
eden-fiscal, eden-frontend), cada um em worktree isolado, com integração
seletiva manual (barrel, app.module.ts, migrations renomeadas/aplicadas,
build/typecheck/teste ao vivo antes de cada merge).
## Backend
- MFA/TOTP (`apps/api/src/auth/mfa.service.ts`, `crypto/field-encryption.service.ts`):
login em duas etapas quando `mfa_enabled=true`, `FieldEncryptionService`
genérico (AES-256-GCM versionado, ADR-0005) reaproveitado por outros
segredos reversíveis (ex. credencial de equipamento Control iD).
- Inventory: warehouses, stock_movements (ledger append-only), serialized_assets,
asset_assignments, stock_reservations — invariante "serial/patrimônio/MAC
nunca duplicado entre ativos ativos" reforçada por índices únicos parciais
no banco (Master Prompt §15.3).
- Support/Service Desk: tickets com máquina de estados, SLA com pausa/retomada
em `waiting_customer`/`waiting_third_party` (funções puras testáveis).
- HR-Timeclock (fundação): dispositivos Control iD (senha cifrada), import de
AFD com parser + CRC-16/KERMIT, `afd_records` imutável sem exceção nem
para super_admin (sem válvula de escape, diferente de audit_log).
- Fiscal: NCM/CFOP/municípios/naturezas de operação (eden.md módulo 5, MVP de
5 das ~19 tabelas), product_fiscal_profiles com invariante de único
profile ativo por billing_component.
- Billing: subscription→billing_run→invoice idempotente (regenerar em draft
substitui, nunca duplica) e imutável após consolidar (ADR-0008: nunca
confunde faturamento/documento fiscal/recebimento).
- FKs cruzadas fechadas: quotes/client_registrations/contracts.
## Frontend — primeira leva
- `packages/ui`: Design System real extraído do tema DreamsERP comprado —
achado que mudou o escopo: o tema já vem em React+Tailwind (não só
Angular), tokens visuais reais portados (cores, Geist como fonte real,
divergindo da doc antiga que citava Nunito/Poppins).
- `apps/core-web`: login real (com suporte a MFA), sessão, shell autenticado
via `AppShell` — substitui a tela estática "Bootstrap da Fase 1".
## Bugs reais encontrados e corrigidos (todos via teste ao vivo, não leitura)
1. **Segurança**: o token de desafio MFA era aceito como access token normal
pelo `JwtAuthGuard` (mesma assinatura, nenhuma checagem da claim
`purpose`) — bypass total do segundo fator. Explorado e fechado ao vivo
contra o container real; teste de regressão em `test/mfa.spec.ts`.
2. Crase solta dentro de comentário SQL na migration de hr-timeclock quebrava
o template literal JS (`SyntaxError` ao rodar a migration).
3. CORS ausente por completo na API.
4. `turbo.json` não declarava `VITE_API_BASE_URL` como input de build — o
Turborepo cacheava o bundle do frontend ignorando mudança na env var.
5. `compose.yaml` usava `${VITE_API_BASE_URL:-/api/v1}`, que colidia com a
variável de mesmo nome do `.env` local — o default nunca era aplicado.
6. Não existia `.dockerignore` — todo build copiava node_modules/.git/
worktrees de subagentes (665MB) para dentro do contexto Docker.
Decisão do operador incorporada: a API nunca tem domínio público próprio
(só IP interno) — os 3 frontends chamam por caminho relativo `/api/v1`,
proxy externo roteia same-origin; CORS só é relevante para dev local.
Verificado: `pnpm turbo run typecheck build` (28/28), `pnpm --filter
@eden/api test` (30/30, cresceu de 15), todo o lote testado ao vivo via
curl contra os containers reais reconstruídos, incluindo os 6 bugs acima
já fechados e confirmados, e a trilha de auditoria conferida no Postgres.
Claude-Session: https://claude.ai/code/session_01BcWWuKfykGtUweXmHD6cKx
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
111
pnpm-lock.yaml
generated
111
pnpm-lock.yaml
generated
@@ -56,6 +56,9 @@ importers:
|
||||
jsonwebtoken:
|
||||
specifier: ^9.0.2
|
||||
version: 9.0.3
|
||||
otplib:
|
||||
specifier: ^13.5.0
|
||||
version: 13.5.0
|
||||
reflect-metadata:
|
||||
specifier: ^0.2.2
|
||||
version: 0.2.2
|
||||
@@ -99,12 +102,18 @@ importers:
|
||||
|
||||
apps/core-web:
|
||||
dependencies:
|
||||
'@eden/ui':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/ui
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
react-dom:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1(react@18.3.1)
|
||||
react-router-dom:
|
||||
specifier: ^6.28.1
|
||||
version: 6.30.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^18.3.18
|
||||
@@ -305,6 +314,9 @@ importers:
|
||||
'@types/react':
|
||||
specifier: ^18.3.18
|
||||
version: 18.3.31
|
||||
tailwindcss:
|
||||
specifier: ^3.4.17
|
||||
version: 3.4.19(tsx@4.23.13)
|
||||
typescript:
|
||||
specifier: ^5.7.3
|
||||
version: 5.9.3
|
||||
@@ -1088,6 +1100,10 @@ packages:
|
||||
resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
|
||||
engines: {node: ^14.21.3 || >=16}
|
||||
|
||||
'@noble/hashes@2.4.0':
|
||||
resolution: {integrity: sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -1105,6 +1121,24 @@ packages:
|
||||
engines: {node: '>=8.0.0', npm: '>=5.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@otplib/core@13.5.0':
|
||||
resolution: {integrity: sha512-2rURdkYkb3BDhMs3j/oCCPTve1ybJ6ruLfLfSe1ZSPV+y6RFTbLfAuT0m0ZCnps88ogkIq9t/+Li/kg5RofQwQ==}
|
||||
|
||||
'@otplib/hotp@13.5.0':
|
||||
resolution: {integrity: sha512-1EwwAti05CeWJn4xXOVMBK9N6dIJlJw/cQkQyiL9OVlkeS452wAve3ffodi+5IUZdWoF1wxVug38RisdxDqDWw==}
|
||||
|
||||
'@otplib/plugin-base32-scure@13.5.0':
|
||||
resolution: {integrity: sha512-3JEIHindUMiIeNL0jXepSAkZ/7IkWq4sPdG9eXK0lrMXZG9RdKu5oz/4tuuTodtsFUPYhKlRMqv5xUvQIkMIaA==}
|
||||
|
||||
'@otplib/plugin-crypto-noble@13.5.0':
|
||||
resolution: {integrity: sha512-fihOAGFvc4b8XTKyIK3jifFP2nLrUNc2bOaJ3UkUKJjy1XI2FbHKG0WmOH+jFsPfa6VsOnouCnEmSznDxIe0pA==}
|
||||
|
||||
'@otplib/totp@13.5.0':
|
||||
resolution: {integrity: sha512-GD9LzQnbHDwXCp79s8AaiA5cJR6luX5fEr9AN32AWx4ooRvllPbMkG80gHSA0jbGCnAIX/ChyOtTZ8tgJHMkSg==}
|
||||
|
||||
'@otplib/uri@13.5.0':
|
||||
resolution: {integrity: sha512-LsL1hqTEgJHY40U2eb/Qp6OR1tKkNGwHQTrqqjIuJj0cawGWkLEmeUFK1MHzWBQ4bgy5IdeB5Et7gIDSAH/CRw==}
|
||||
|
||||
'@paralleldrive/cuid2@2.3.1':
|
||||
resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==}
|
||||
|
||||
@@ -1112,6 +1146,10 @@ packages:
|
||||
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@remix-run/router@1.23.4':
|
||||
resolution: {integrity: sha512-q7j5geK7xs3UJSdm9/iytUNclBnLmYx1EnSeCFXHPeutdqgIMeFeHtUZgS3EhlKxdBEAu8OwtJCwmLrEzpSs7Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.27':
|
||||
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
|
||||
|
||||
@@ -1265,6 +1303,9 @@ packages:
|
||||
'@scarf/scarf@1.4.0':
|
||||
resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
|
||||
|
||||
'@scure/base@2.4.0':
|
||||
resolution: {integrity: sha512-thZ1TuJwFwBblOhgsjDKvvGirBxNp+wSvY/DR6tJBJOTDhdAAcHJ8Vbr2eFnqaxeca4+t0i9KBf+uHYGWwZORg==}
|
||||
|
||||
'@smithy/core@3.33.3':
|
||||
resolution: {integrity: sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
@@ -2286,6 +2327,9 @@ packages:
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
otplib@13.5.0:
|
||||
resolution: {integrity: sha512-RpcC6aq4rANX6MverMuU3pqHVLgMPO7vl6qR8ga7YzoEHlPJknJ58cSVAaf3jBYnasiQVQHXXl2mj/4pYlsVEQ==}
|
||||
|
||||
package-json-from-dist@1.0.1:
|
||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||
|
||||
@@ -2461,6 +2505,19 @@ packages:
|
||||
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
react-router-dom@6.30.6:
|
||||
resolution: {integrity: sha512-0RHKZz7wwffvkU+2MFVT2NnjK44ssLEV+m0CAJaS2Ksmorrwj7WxH00jO0SOCW26/tINUnJHToXblDs33I38YQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=16.8'
|
||||
react-dom: '>=16.8'
|
||||
|
||||
react-router@6.30.6:
|
||||
resolution: {integrity: sha512-5HfK7k5im7LTOB0EqCQmfvy4C13G92Ssj1VTmouTK3AJvyjKTnFuCV0vcMAD/JS+JC4DvDIBRrlAeJIFjh5VWg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=16.8'
|
||||
|
||||
react@18.3.1:
|
||||
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -3551,6 +3608,8 @@ snapshots:
|
||||
|
||||
'@noble/hashes@1.8.0': {}
|
||||
|
||||
'@noble/hashes@2.4.0': {}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
@@ -3571,12 +3630,41 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
'@otplib/core@13.5.0': {}
|
||||
|
||||
'@otplib/hotp@13.5.0':
|
||||
dependencies:
|
||||
'@otplib/core': 13.5.0
|
||||
'@otplib/uri': 13.5.0
|
||||
|
||||
'@otplib/plugin-base32-scure@13.5.0':
|
||||
dependencies:
|
||||
'@otplib/core': 13.5.0
|
||||
'@scure/base': 2.4.0
|
||||
|
||||
'@otplib/plugin-crypto-noble@13.5.0':
|
||||
dependencies:
|
||||
'@noble/hashes': 2.4.0
|
||||
'@otplib/core': 13.5.0
|
||||
|
||||
'@otplib/totp@13.5.0':
|
||||
dependencies:
|
||||
'@otplib/core': 13.5.0
|
||||
'@otplib/hotp': 13.5.0
|
||||
'@otplib/uri': 13.5.0
|
||||
|
||||
'@otplib/uri@13.5.0':
|
||||
dependencies:
|
||||
'@otplib/core': 13.5.0
|
||||
|
||||
'@paralleldrive/cuid2@2.3.1':
|
||||
dependencies:
|
||||
'@noble/hashes': 1.8.0
|
||||
|
||||
'@phc/format@1.0.0': {}
|
||||
|
||||
'@remix-run/router@1.23.4': {}
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.27': {}
|
||||
|
||||
'@rollup/pluginutils@5.4.0(rollup@4.63.1)':
|
||||
@@ -3664,6 +3752,8 @@ snapshots:
|
||||
|
||||
'@scarf/scarf@1.4.0': {}
|
||||
|
||||
'@scure/base@2.4.0': {}
|
||||
|
||||
'@smithy/core@3.33.3':
|
||||
dependencies:
|
||||
'@smithy/types': 4.17.2
|
||||
@@ -4773,6 +4863,15 @@ snapshots:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
otplib@13.5.0:
|
||||
dependencies:
|
||||
'@otplib/core': 13.5.0
|
||||
'@otplib/hotp': 13.5.0
|
||||
'@otplib/plugin-base32-scure': 13.5.0
|
||||
'@otplib/plugin-crypto-noble': 13.5.0
|
||||
'@otplib/totp': 13.5.0
|
||||
'@otplib/uri': 13.5.0
|
||||
|
||||
package-json-from-dist@1.0.1: {}
|
||||
|
||||
parseurl@1.3.3: {}
|
||||
@@ -4918,6 +5017,18 @@ snapshots:
|
||||
|
||||
react-refresh@0.17.0: {}
|
||||
|
||||
react-router-dom@6.30.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@remix-run/router': 1.23.4
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-router: 6.30.6(react@18.3.1)
|
||||
|
||||
react-router@6.30.6(react@18.3.1):
|
||||
dependencies:
|
||||
'@remix-run/router': 1.23.4
|
||||
react: 18.3.1
|
||||
|
||||
react@18.3.1:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
||||
Reference in New Issue
Block a user