---
title: Arquitetura
persona: all
category: Documentação técnica
version: 1.0.0
updated: 2026-06-09
---

# Arquitetura

O MyFinAI é um **monólito FastAPI autocontido sobre PostgreSQL** que *extrai*
padrões comprovados dos demais sistemas Lopettia em vez de se acoplar a eles: o
razão de partida dobrada e a auditoria encadeada do AICOS, o RLS multi-tenant do
Kairos, o BYOK Fernet+HKDF do Servitium, os agentes LAF do AIaaSAgents. Um único
serviço, uma única base de dados, sem dependências entre produtos.

## O plano

```
Caddy (TLS, reverse proxy)
  ├── /            → site de marketing estático (/srv/myfinai)
  ├── /app, /app/* → o console SPA (contêiner)
  └── /api/*       → a API FastAPI (contêiner)

Contêiner myfinai-api  ──►  PostgreSQL compartilhado
  (rede lopettia_net)         papel myfinai_app (NOSUPERUSER, NOBYPASSRLS)
                              base de dados myfinai
```

O contêiner expõe a porta 8000 dentro de `lopettia_net`; o Caddy roteia por
rota. O site de marketing vive como estático no host (`/opt/www/myfinai`,
que o Caddy monta como `/srv`).

## Uma identidade, vários espaços

O modelo de dados separa **quem você é** de **onde você trabalha**:

- Um **usuário** é uma identidade com um login.
- Um **inquilino** (`tenants`) é um espaço de trabalho, do tipo `business`,
  `household` ou `platform`.
- Uma **associação** (`memberships`) conecta usuário ↔ inquilino com um papel.

Uma pessoa pode pertencer a vários espaços (Felipe é `owner` do café e
`homeowner` do lar). Ao **mudar de espaço** (`/api/auth/switch`) o sistema
valida a associação e reemite o token com o novo `tenant_id` e papel ativos. O
livro do negócio e o do lar **nunca se misturam**.

## RLS: o isolamento vive na base de dados

Cada requisição autenticada roda dentro de uma transação que fixa duas variáveis
do Postgres que as políticas RLS leem:

```sql
SELECT set_config('app.current_tenant_id', :tenant, true);
SELECT set_config('app.is_platform',       :flag,   true);
```

Como são usadas com `is_local = true` (equivalente a `SET LOCAL`), elas se reiniciam ao
terminar a transação e **não vazam entre conexões do pool**. Todas as
tabelas com dados de inquilino levam `FORCE ROW LEVEL SECURITY`, e a aplicação
se conecta com um papel **sem** `BYPASSRLS`: nem um erro da aplicação pode ver
outro inquilino. A camada de identidade (login, switch) usa uma sessão `raw_db` sem
contexto de inquilino, e nunca consulta dados de negócio.

## O livro de partida dobrada

Toda operação financeira é registrada como um **lançamento** (`journal_entries`) com
suas **linhas** (`journal_lines`), cada uma débito ou crédito. O sistema rejeita um
lançamento que não fecha, de modo que o **balancete de verificação** (∑DR = ∑CR) está
fechado por construção, não por revisão posterior. Os painéis (caixa, margem,
resultado) são **calculados** a partir do livro em cada carregamento; não há cifras paralelas.

## Camadas extraídas

| Camada | Arquivo | Origem do padrão |
|---|---|---|
| Configuração | `app/config.py` | Educatio |
| Sessões RLS | `app/db.py` | Kairos |
| Identidade / JWT | `app/security.py` | Servitium |
| BYOK (cifragem de chaves) | `app/crypto.py` | Servitium |
| Auth + workspaces | `app/routers/auth.py` | novo (memberships) |
| Dashboards por persona | `app/routers/dash.py` | novo |
| Superfícies de dados | `app/routers/data.py` | novo |
| Agente Norte | `app/routers/agent.py` | AIaaSAgents (LAF) |

O esquema e as políticas vivem em `api/migrations/V001…V005`. Para implantar usa-se
o padrão operador-laptop da Lopettia (`api/infra/scripts/deploy.sh`), nunca
runners de CI.
