Files
Projeto-Portaria/prompts/FASE-1-dominio-e-dados.md

4.5 KiB

FASE 1 — Domínio compartilhado e camada de dados

Objetivo

Modelo de domínio em shared/, schema completo no Postgres, infraestrutura de confiabilidade (outbox, fila, idempotência) funcionando, e autenticação. Ainda sem endpoints de negócio.

Pré-requisitos

FASE 0 concluída. Ler docs/02-MODELO-DE-DADOS.md inteiro e docs/01-ARQUITETURA.md §4 e §5.

Tarefas

1. Domínio em shared/

state/VisitState.kt e VisitStateMachine.kt conforme 01-ARQUITETURA.md §4 e o diagrama de 03-FLUXOS-E-CONTRATOS.md §1. A máquina de estados é a peça mais importante deste módulo — teste toda transição válida e inválida.

model/ — enums e value objects: VisitKind, DeliveryRule, MemberRole, MediaKind, DegradedMode, FeatureKey, GeoPoint.

dto/ — DTOs @Serializable de todos os contratos de 03-FLUXOS-E-CONTRATOS.md §6.

validation/ — validação de nome de visitante, telefone E.164, formato de unidade, tamanho de mídia. Usada no cliente e no servidor.

2. Migrations Flyway

As dez migrations de 02-MODELO-DE-DADOS.md §11, exatamente na ordem indicada. Pontos que não podem ser esquecidos:

  • tenant_id uuid NOT NULL em toda tabela de negócio
  • version integer NOT NULL DEFAULT 0 onde há escrita concorrente
  • Nenhum ON DELETE CASCADE em dado auditável
  • Índice GIST em gates.location
  • Índice parcial em visits para estados ativos
  • V8 revoga UPDATE/DELETE em audit_log para o usuário da aplicação
  • V9 cria as policies de RLS e as deixa desativadas

3. jOOQ

Geração de código a partir do schema migrado (Testcontainers no build, não banco local). Repositórios para os agregados principais. Todo repositório recebe tenantId como parâmetro obrigatório — sem default, sem opcional. É o que torna a ativação futura do multi-tenant um interruptor.

4. Outbox transacional

interface OutboxPublisher {
    fun enfileirar(evento: DomainEvent)   // participa da transação corrente
}

Publisher que lê outbox_events pendentes com FOR UPDATE SKIP LOCKED, despacha, marca published_at, e em falha aplica backoff exponencial (next_attempt_at) e grava last_error. Após 10 tentativas, alerta.

Teste obrigatório: transação que falha após enfileirar() não pode deixar evento na tabela. É exatamente a garantia que o padrão existe para dar.

5. Fila de jobs e ShedLock

interface JobScheduler {
    fun agendar(tipo: JobType, payload: JsonObject, quando: Instant): Long
    fun cancelar(jobId: Long)
}

Poller @Scheduled(fixedDelay = 1000) + @SchedulerLock, consumindo com FOR UPDATE SKIP LOCKED. Handlers registrados por JobType.

Teste obrigatório com duas instâncias simultâneas: cada job executa exatamente uma vez.

6. Idempotência

Filtro/interceptor que lê Idempotency-Key, consulta idempotency_keys, e:

  • chave nova → executa e grava a resposta
  • chave repetida com mesmo request_hash → devolve a resposta original
  • chave repetida com corpo diferente → 422 IDEMPOTENCY_KEY_REUSE

7. Segurança base

Spring Security com três cadeias de filtro conforme 06-LGPD-E-SEGURANCA.md §6: /api/v1/visitor/** (JWT efêmero), /api/v1/app/** (OIDC), /api/v1/admin/** (OIDC + MFA).

Emissão e validação do JWT de visitante: 15 min, escopo de uma visita, atado a gateId e à sessão — não ao IP: IP de celular muda no meio da sessão (CGNAT, troca de torre) e derrubaria visitante legítimo.

Conexões ao banco: Flyway como portaria (dono), aplicação como portaria_app — sem essa separação, o revoke do audit_log e a RLS futura são decorativos (dono ignora ambos). Ver 02-MODELO-DE-DADOS.md §8.

8. Auditoria

Aspecto que grava em audit_log toda mutação de entidade auditável, com before/after em jsonb.

Critérios de aceite

  • ./gradlew :shared:allTests verde (JVM + iOS)
  • Cobertura de 100% das transições da máquina de estados, válidas e inválidas
  • flyway migrate do zero cria o schema completo
  • jOOQ gera código a partir do schema migrado
  • Teste: rollback de transação não deixa evento no outbox
  • Teste: duas instâncias do poller executam cada job exatamente uma vez
  • Teste: UPDATE em audit_log falha por permissão do banco
  • Teste: chave de idempotência repetida devolve a resposta original
  • Teste: repositório sem tenantId não compila

Não faça nesta fase

  • Endpoints de negócio (FASE 2)
  • Integração com LiveKit, FCM ou APNs
  • Qualquer tela
  • Ativar RLS