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.
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).
O modelo de dados separa quem você é de onde você trabalha:
tenants) é um espaço de trabalho, do tipo business, household ou platform.
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.
Cada requisição autenticada roda dentro de uma transação que fixa duas variáveis do Postgres que as políticas RLS leem:
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.
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.
| 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.