Files
gerador-proposta/docs/gerador-propostas/ARCHITECTURE.md
T
2026-10-03 00:00:45 -03:00

8.9 KiB

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.

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

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.