# 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 ```kotlin 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 ```kotlin 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