Modèle de données
Source de vérité : ../../src/db/schema/.
Source de vérité : ../../src/db/schema/.
Diagramme synthétique
users (1) ──┬── (N) conversations ── (N) messages
│ └── projectId → projects
├── (N) projects
├── (N) provider_keys (encrypted)
├── (N) connector_keys (encrypted)
├── (N) mcp_servers
├── (N) document_folders (self-ref parentFolderId)
├── (N) documents ── (N) document_chunks (vector 1024)
│ └── parentDocumentId → documents (versioning)
│ └── folderId → document_folders
│ └── projectId → projects
├── (N) workflows
└── (N) tabular_reviews ── (N) tabular_review_rows
cabinet_settings (singleton id=1)
audit_log (append-only)Tables principales
users
UUID PK, email unique, name, passwordHash (bcrypt cost 12), avatarUrl,
role (admin | member), isActive, lastLogin, createdAt.
provider_keys (clés API providers IA, chiffrées)
userIdFKtypeenum :mistral/scaleway/ovh/albert/anthropic/openai/openai_compatiblelabelunique par (user, label)apiKeyCiphertext+apiKeyIv+apiKeyTag— blob AES-256-GCMbaseUrl(optionnel, pour openai_compatible)isActive,isDefault,lastTestedAt,lastTestStatus
connector_keys (PISTE, Pappers, chiffrées)
Même structure que provider_keys mais pour les connecteurs juridiques.
Les credentials sont sérialisés en JSON avant chiffrement (parce qu'un
connecteur peut avoir plusieurs champs : client_id + client_secret
pour PISTE).
documents
UUID PK, userId FK, filename, contentType, sizeBytes,
storageKey (clé S3), extractedText (texte plein, capé 500k chars),
extractionStatus (ok / truncated / failed), extractionError,
parentDocumentId (auto-référencé pour le versioning), version (int),
folderId FK, projectId FK.
document_chunks (vecteurs RAG)
UUID PK, documentId FK, chunkIndex, content (texte du chunk),
embedding (vector(1024), Mistral mistral-embed).
conversations et messages
conversations: title, providerKeyId (utilisé par défaut), modelId, projectId, pinnedAt, updatedAtmessages: role (user/assistant), content (text), parts (JSONB : array de{ type, text | toolCallId | toolName | input | output }pour rejouer les pills de tool au reload), inputTokens, outputTokens, modelId
audit_log
Append-only. userId (acteur, nullable), action (kebab-case namespacé),
target, meta (jsonb), createdAt.
Autres tables
document_folders: self-référencée, hiérarchie illimitéeprojects: conteneurs dossier client, scope usertabular_reviews+tabular_review_rows: analyses tabulaires async vianext/server::after()workflows: bibliothèque de prompts cabinet, scope usermcp_servers: serveurs MCP par user, avec tools cachés en JSONcabinet_settings: singleton (id=1) avec nom du cabinet, footer des documents générés, mention légale
Versioning des documents
Un upload "nouvelle version" crée un nouveau documents.id, avec
parentDocumentId pointant vers la racine de la famille et version
incrémenté. La page Documents groupe par famille, affichant la
version la plus récente avec un historique repliable.
Cf. src/app/api/documents/upload/route.ts:replacesId.
Migrations
Gérées par Drizzle Kit :
npm run db:generate # diff schéma TS vs migrations
npm run db:migrate # applique migrations en attente
npm run db:push # push direct (dev seulement)
npm run db:studio # UI navigateur pour explorer la DBLes migrations vivent dans drizzle/ (généré, pas commité en v0.1 —
prévu pour v0.1.x une fois le schéma stabilisé).
Architecture — vue d'ensemble
provider keys actifs + documents disponibles + workflows 3. Client Component chat-shell.tsx : useChat({ api: "/api/chat" }) 4. API route /api/chat : - Aut…
ADR 0001 — Bring Your Own Key (BYOK)
Une plateforme IA juridique manipule des documents et conversations sous secret professionnel. La législation française (article 226-13 du Code pénal, RGP…