Implementação MVP
This commit is contained in:
commit
807b3d72cd
54 files changed
+5241
No files matched your search
@@ -0,0 +1,146 @@
|
||||
# Arquitetura — Gerador de Propostas
|
||||
|
||||
Estado: prompt 01. Baseline oficial: repositório standalone, sem código EloPro legado. Decisão em `adr/ADR-000-standalone-application.md`.
|
||||
|
||||
Não há feature de produto nesta fase: nem tela, nem migration, nem upload, nem PDF real, nem e-mail real.
|
||||
|
||||
## 1. Contexto
|
||||
|
||||
O Gerador de Propostas é um produto EloPro independente. O usuário autenticado opera propostas e, conforme o papel, modelos, dentro de um workspace. CRO, Contratos e Apresentação não são pré-requisito do fluxo.
|
||||
|
||||
```text
|
||||
Pessoa de comercial / Admin / Gestor
|
||||
|
|
||||
v
|
||||
Gerador de Propostas (este repositório)
|
||||
|
|
||||
+--> PostgreSQL (Supabase) estados formais, RLS
|
||||
+--> Redis lock, claim de geração, job efêmero
|
||||
+--> Storage privado arquivo de modelo e PDF formal
|
||||
+--> Sentry erro, com scrub
|
||||
+--> Provider de e-mail port; provider não escolhido (P-011)
|
||||
+--> Motor de ingestão port; motor não escolhido (P-010)
|
||||
+--> CRO port opcional; fonte de cliente aberta (P-002)
|
||||
```
|
||||
|
||||
Auth, workspace, papéis e RLS nascem neste repositório. A UI usa os tokens EloPro V2.1 definidos na source of truth, em componentes locais, a partir do prompt 04.
|
||||
|
||||
## 2. Contêineres
|
||||
|
||||
| Contêiner | Papel nesta fase | Fonte de verdade |
|
||||
| --- | --- | --- |
|
||||
| Next.js 16 App Router | Ainda não criado. Entra no prompt 04. | — |
|
||||
| PostgreSQL via Supabase | Ainda sem migration. Estados formais: modelo, proposta, versão, auditoria, e-mail. | Sim, quando existir |
|
||||
| Redis (`ioredis`) | Ainda sem cliente. Lock de edição, claim de geração, estado efêmero de job. | Não |
|
||||
| Storage | Ainda sem bucket. | O ponteiro formal fica no PostgreSQL |
|
||||
| Sentry | Ainda sem SDK. A política de scrub já existe. | Não |
|
||||
| Claude | Ainda sem SDK. Não lê marcadores. | Não |
|
||||
|
||||
PostgreSQL é a fonte dos estados formais. Redis pode perder dados. Se o processo cair depois do commit e antes de apagar o claim, o retry lê o PostgreSQL e devolve a mesma versão.
|
||||
|
||||
## 3. Camadas
|
||||
|
||||
```text
|
||||
src/ui experiência. Vazia até o prompt 04.
|
||||
src/application casos de uso. Hoje: coordenação da geração e o contrato de autorização.
|
||||
src/domain regras e tipos. Não importa aplicação, ports, infra, UI nem integrações.
|
||||
src/ports interfaces que a aplicação exige da infra.
|
||||
src/infrastructure adapters. Hoje só duplos em memória, para teste.
|
||||
src/integrations/elopro contratos com outros produtos. Sem banco nem sessão compartilhada.
|
||||
src/observability scrub antes de log/Sentry.
|
||||
```
|
||||
|
||||
Regra de dependência, também no ESLint:
|
||||
|
||||
- `domain` não importa as outras camadas.
|
||||
- `ports` importa só `domain`.
|
||||
- `application` não importa `infrastructure` nem `ui`. Teste pode importar duplo.
|
||||
- `infrastructure` não importa `application` nem `ui`.
|
||||
- `integrations` não importa `application`, `infrastructure` nem `ui`.
|
||||
|
||||
`app/` (rotas) fica para o prompt 04 e deve chamar aplicação, não o banco direto a partir de componente cliente.
|
||||
|
||||
## 4. Componentes de domínio
|
||||
|
||||
| Módulo | Responsabilidade | Fora dele |
|
||||
| --- | --- | --- |
|
||||
| Models | Modelos, limite 10, versão de modelo, ativação | Não inicia proposta |
|
||||
| Proposals | Rascunho, status `Rascunho`/`Gerada`, versão formal | Não envia e-mail |
|
||||
| Generation | Commit do PDF, número e versão na mesma transação | Não escolhe o escopo de `PROP` |
|
||||
| Delivery | E-mail do `pdfRef` exibido | Não muda status |
|
||||
| Audit | Eventos mínimos | Retenção é P-007 |
|
||||
| Branding | Logo obrigatório, cores opcionais | Não é o design system da UI |
|
||||
| CRO | Pré-preenchimento quando houver contexto | O standalone funciona com `no-context` |
|
||||
|
||||
Papéis previstos no tipo `WorkspaceRole`: `owner`, `admin`, `gestor`, `agent`. O `WorkspaceAuthorizer` não tem implementação. Não existe adapter que responda `allowed` por omissão. O prompt 03 implementa a matriz, no servidor, com `workspace_id` da sessão.
|
||||
|
||||
Lock de edição: `EDIT_LOCK_TTL_MS` = 10 minutos. A interface `DistributedLockService` não tem tomada forçada.
|
||||
|
||||
## 5. Geração: idempotência e transação
|
||||
|
||||
Entrada: workspace, proposta, rascunho, ator, chave de idempotência, hash do conteúdo, versão do modelo e, se já existir, o número comercial. A chave efetiva no store e no ledger inclui workspace + proposta + chave. Outro workspace não reaproveita o commit.
|
||||
|
||||
Ordem:
|
||||
|
||||
1. Se o PostgreSQL já tem commit da mesma chave e da mesma impressão digital (rascunho, versão do modelo, hash), devolver essa versão com `replayed: true`. Não renderiza de novo e não aloca número.
|
||||
2. Se a mesma chave já tem outra impressão digital, responder `conflict`. Não cria versão.
|
||||
3. Claim efêmero (Redis). Mesma impressão em andamento: `in-progress`. Outra impressão em andamento: `conflict`.
|
||||
4. Renderizar para staging. Falha aqui não abre transação, não aloca número e não grava versão. O claim é liberado para o retry.
|
||||
5. Commit único no store formal:
|
||||
- promove o staging para `pdfRef`;
|
||||
- se a proposta ainda não tem número e esta é a primeira versão, reserva o número dentro dessa mesma unidade;
|
||||
- insere a versão imutável apontando para esse `pdfRef`;
|
||||
- confirma a reserva.
|
||||
6. Se qualquer passo do commit falha, a reserva sofre rollback, o `pdfRef` promovido é desfeito e o staging é descartado. `commercialNumberAssigned` e `formalVersionCreated` ficam falsos. A falha não é gravada como resultado da chave: o retry pode tentar de novo.
|
||||
7. A segunda versão só segue se `existingCommercialNumber` já veio do commit anterior. O coordenador não pede outro número. Quem aloca é só a primeira versão, e só dentro do commit.
|
||||
|
||||
O duplo de teste emite `TEST-######` para deixar óbvio que não é a sequência de produção. O escopo real espera o ADR-001.
|
||||
|
||||
Não há método de update ou delete de versão formal. O objeto commitado é congelado no duplo. A garantia de produção será a ausência de `UPDATE`/`DELETE` na policy e o fluxo, no prompt 02.
|
||||
|
||||
O TTL do claim não é SLO. P-012 segue aberto. Quem ligar o coordinator no futuro passa `attemptTtlMs`.
|
||||
|
||||
## 6. Ports
|
||||
|
||||
| Port | Situação |
|
||||
| --- | --- |
|
||||
| `GenerationCoordinator` | Implementado em `src/application/generation/run-generation.ts` |
|
||||
| `FormalGenerationStore` | Contrato. Duplo em memória nos testes |
|
||||
| `CommercialNumberAllocator` | Contrato transacional (`reserve` / `commit` / `rollback`). Sem adapter de produção |
|
||||
| `ProposalPdfRenderer` | Contrato. Adapter previsto: `@react-pdf/renderer` sobre representação normalizada. Não instalado |
|
||||
| `StagingArtifactStore` | Promote só fica visível se o commit concluir |
|
||||
| `GenerationAttemptLedger` | Contrato do estado efêmero |
|
||||
| `DistributedLockService` | Contrato do lock de edição. Sem force takeover |
|
||||
| `TemplateIngestionService` | Sempre bloqueado: P-006 e depois P-010 |
|
||||
| `ProposalEmailProvider` | Sempre bloqueado: P-001 ou P-011 |
|
||||
| `CroClientPrefillPort` | `no-context` ou `pending-source` |
|
||||
| `WorkspaceAuthorizer` | Só a interface |
|
||||
|
||||
## 7. Sentry e correlação
|
||||
|
||||
`CorrelationContext` carrega `requestId`, `jobId`, `workspaceId` e `actorId`. O `jobId` nasce quando houver job de geração. O `requestId` nasce na borda HTTP se o chamador não enviar um.
|
||||
|
||||
Propagação: a borda cria o contexto, a aplicação repassa aos ports, o job carrega o mesmo `requestId` e preenche `jobId`. Tags previstas no Sentry: esses ids mais `proposalId` ou `modelId` quando a operação for sobre um registro.
|
||||
|
||||
`scrubForTelemetry` roda antes de `captureException` / breadcrumb. Remove, em qualquer profundidade, documento, template, snapshot, análise, e-mail, assunto, corpo, telefone, CPF, CNPJ, logo e nome de arquivo. Não envia o PDF nem o modelo. O SDK do Sentry entra junto com a primeira borda que possa falhar (prompt 14, ou antes se o prompt 04 já expuser erro de runtime). Até lá a função é a política, não um cliente.
|
||||
|
||||
## 8. Stack instalada agora
|
||||
|
||||
TypeScript 5, ESLint 9 e Vitest. O `tsconfig` usa `moduleResolution: Bundler` e strict, alinhado ao App Router que virá.
|
||||
|
||||
Ainda não instalados, de propósito: Next.js, React, Tailwind, Supabase, `ioredis`, Sentry, `@anthropic-ai/sdk`, `@react-pdf/renderer`. Não há uso para eles nesta fase.
|
||||
|
||||
## 9. Pendências que continuam Proposed
|
||||
|
||||
- ADR-001 / P-009 — escopo da sequência
|
||||
- ADR-002 / P-010 — motor de ingestão
|
||||
- ADR-003 / P-011 — provider de e-mail
|
||||
- ADR-004 / P-006 — tamanho, antivírus, sandbox, extração parcial
|
||||
|
||||
P-001, P-002, P-003, P-004, P-005, P-007, P-008 e P-012 também seguem abertos e não têm ADR de decisão.
|
||||
|
||||
## 10. O que o prompt 02 pode fazer sem novas decisões
|
||||
|
||||
Pode criar o schema do workspace, papéis e das tabelas de domínio da source of truth, com RLS por `workspace_id`, desde que não feche os ADRs acima.
|
||||
|
||||
Não criar sequence, unique do número comercial, check de provider, limite de arquivo, job de antivírus nem enum que escolha o motor de ingestão. Detalhe em cada ADR.
|
||||
Reference in new issue
Block a user