Base de données — Schéma SQLite

Informations générales

Diagramme ER (Phase 52.1)

erDiagram
    users ||--o{ projects : owns
    users ||--|| account_settings : has
    users ||--o{ recovery_keys : holds
    users ||--o{ subscriptions : pays
    users ||--o{ invites : creates
    users ||--o{ chat_messages : sends
    users ||--o{ trial_credits : claims
    users ||--o{ activity_log : generates

    projects ||--o{ project_phases : tracks
    projects ||--o{ pinned_notes : pins
    projects ||--o{ chat_messages : contains
    projects ||--o{ timeline_events : records
    projects ||--o{ github_links : links
    projects ||--o{ skill_benchmarks : runs
    projects ||--o{ marketplace_cache : caches

    github_links ||--o{ github_events : receives

    invites }o--|| users : "consumed by"
    subscriptions ||--o{ stripe_events : "logs idempotency"

    users {
        TEXT id PK
        TEXT email UK
        TEXT password_hash
        TEXT role "admin · user"
        TEXT google_id UK
        TEXT github_id UK
        INTEGER email_verified
        INTEGER two_factor_enabled
        TEXT last_login
    }

    projects {
        INTEGER id PK
        TEXT technical_name
        TEXT owner_id FK
        TEXT display_name
        TEXT notebook_id "NotebookLM"
        INTEGER is_archived
    }

    chat_messages {
        INTEGER id PK
        TEXT project_name
        TEXT worker_id
        TEXT role "user · assistant · system"
        TEXT content "AES-256-GCM if encrypted=1"
        INTEGER encrypted
        TEXT timestamp
    }

    subscriptions {
        TEXT user_id PK
        TEXT plan "free · min · max · beta"
        TEXT stripe_customer_id
        TEXT status
    }

    invites {
        TEXT code PK "arc-XXXX-XXXX"
        TEXT created_by FK
        TEXT used_by FK
        TEXT parent_invite_code
        TEXT status "active · used · revoked"
    }

    recovery_keys {
        INTEGER id PK
        TEXT user_id FK
        TEXT encrypted_key
        TEXT key_hint "first 4 chars"
    }

    trial_credits {
        TEXT email PK
        INTEGER credits_used
        TEXT first_used_at
    }

30+ tables réparties sur 35 migrations. Auth + projets + données workspace + intelligence + billing + bêta gating + audit d'export de contexte. Toutes les contraintes FK sont appliquées.

Tables

users — Utilisateurs

Colonne Type Description
id TEXT PK UUID
email TEXT UNIQUE Email (obligatoire)
password_hash TEXT Hash du mot de passe (bcrypt)
role TEXT 'admin' ou 'user'
name TEXT Nom
avatar_url TEXT URL de l'avatar
google_id TEXT UNIQUE Google OAuth ID
github_id TEXT UNIQUE GitHub OAuth ID
email_verified INTEGER 0/1
two_factor_enabled INTEGER 0/1
totp_secret TEXT Secret TOTP (2FA)
last_login TEXT ISO 8601
created_at TEXT ISO 8601

projects — Projets

Colonne Type Description
id INTEGER PK Auto
technical_name TEXT Nom technique (unique par owner)
owner_id TEXT FK→users Propriétaire
display_name TEXT Nom affiché
description TEXT Description
color TEXT Couleur (hex)
icon TEXT Icône
is_archived INTEGER 0/1
sort_order INTEGER Ordre (défaut 999)
notebook_id TEXT NotebookLM ID
project_protocol TEXT Protocole du projet
created_at, updated_at TEXT Timestamps

UNIQUE(technical_name, owner_id)

account_settings — Paramètres du compte

Colonne Type Description
user_id TEXT PK FK→users
anthropic_key TEXT Clé API Anthropic
openai_key TEXT Clé API OpenAI
name TEXT Nom
role TEXT Rôle
auto_harvest INTEGER Collecte automatique de skills 0/1

chat_messages — Historique des chats

Colonne Type Description
id INTEGER PK Auto
project_name TEXT Projet
worker_id TEXT Worker
role TEXT 'user', 'assistant', 'system'
content TEXT Contenu du message (chiffré AES-256-GCM si encrypted=1)
encrypted INTEGER 0=plaintext, 1=vault-encrypted (Phase 45.3)
attachments TEXT Tableau JSON
timestamp TEXT ISO 8601
metadata TEXT Objet JSON

INDEX(project_name, worker_id, timestamp DESC)

recovery_keys — Clés de récupération (Phase 45.4)

Colonne Type Description
id INTEGER PK Auto
user_id TEXT FK→users Propriétaire
encrypted_key TEXT Master key chiffrée
key_hint TEXT Indice (4 premiers caractères)
created_at TEXT ISO 8601
used_at TEXT Date d'utilisation
revoked INTEGER 0=actif, 1=révoqué

INDEX(user_id)

github_links — Liaisons dépôts GitHub (Phase 49.3)

Colonne Type Description
id INTEGER PK Auto
project_name TEXT Projet Arc OS
owner TEXT Propriétaire du dépôt GitHub
repo TEXT Nom du dépôt GitHub
webhook_secret TEXT UNIQUE Hex 32 octets pour la validation HMAC-SHA256
created_at TEXT ISO 8601
created_by TEXT FK→users Qui a créé le link

UNIQUE(project_name, owner, repo) — multi-repo par projet autorisé. INDEX(project_name), INDEX(webhook_secret)

github_events — Log des événements GitHub (Phase 49.3.1)

Colonne Type Description
id INTEGER PK Auto
link_id INTEGER FK→github_links CASCADE on link delete
project_name TEXT Projet Arc OS (dénormalisé pour la vitesse de requête)
event_type TEXT push, pull_request, workflow_run, issues
action TEXT sous-action (opened/closed/success/failure)
summary TEXT Chaîne d'affichage pré-formatée
url TEXT Lien profond GitHub
actor TEXT Nom d'utilisateur GitHub
created_at TEXT ISO 8601

INDEX(project_name, created_at DESC), INDEX(link_id)

skills_global — Skills globaux

Colonne Type Description
id INTEGER PK Auto
name TEXT UNIQUE Nom
description TEXT Description
category TEXT Catégorie (défaut 'general')
content TEXT Contenu du skill (Markdown)
triggers TEXT Tableau JSON de déclencheurs
keywords TEXT Tableau JSON de mots-clés
eval_rules TEXT Tableau JSON de règles
tool_code_ts TEXT Implémentation TypeScript
version INTEGER Version (auto-incrémentée)
status TEXT 'active', 'draft', 'deprecated', 'archived'

skills_project_forks — Forks de skills

Colonne Type Description
id INTEGER PK Auto
project_name TEXT Projet
skill_id INTEGER FK→skills_global Skill parent (CASCADE)
content TEXT Contenu personnalisé
triggers, keywords, eval_rules, tool_code_ts TEXT Surcharges
version INTEGER Version du fork

UNIQUE(project_name, skill_id)

skill_evolution_logs — Historique des modifications de skills

Colonne Type Description
id INTEGER PK
skill_id INTEGER FK CASCADE
project_name TEXT Projet (nullable)
action TEXT 'created', 'modified', 'applied', 'reverted'
diff_summary TEXT Description des modifications
author TEXT Auteur (défaut 'system')
metadata TEXT JSON

skill_update_requests — PR de skills (Sage)

Colonne Type Description
id INTEGER PK
skill_id INTEGER FK CASCADE
proposed_by TEXT 'sage' (défaut)
status TEXT 'pending', 'approved', 'rejected', 'applied'
current_content TEXT Version actuelle
proposed_content TEXT Version proposée
reason TEXT Raison de la modification

skill_benchmarks — Tests A/B de skills

Colonne Type Description
id INTEGER PK
request_id INTEGER FK→skill_update_requests CASCADE
test_scenario TEXT Scénario de test
old_output, new_output TEXT Résultats
score_old, score_new REAL Scores
judgment_reason TEXT Justification

pinned_notes — Notes épinglées

Colonne Type Description
id INTEGER PK
project_name TEXT Projet
worker_id TEXT Worker
title TEXT Titre
body TEXT Contenu
source_message_id INTEGER Référence vers chat_messages

project_phases — Phases Roadmap

Colonne Type Description
id INTEGER PK
project_name TEXT Projet
phase_id INTEGER Numéro de phase
phase_title TEXT Titre
status TEXT 'PLANNED', 'ACTIVE', 'COMPLETED', 'CANCELLED'
progress REAL 0.0–1.0
deadline TEXT ISO 8601

UNIQUE(project_name, phase_id)

activity_log — Journal d'activité

Colonne Type Description
id INTEGER PK
project_name TEXT Projet
actor TEXT Worker ou utilisateur
event_type TEXT Type d'événement
title TEXT Description
metadata TEXT JSON
created_at TEXT ISO 8601

INDEX(created_at DESC), INDEX(project_name, created_at DESC), INDEX(event_type, created_at DESC), INDEX(actor, created_at DESC)

system_configs — Paramètres système

Colonne Type Description
key TEXT PK Clé
value TEXT Valeur
description TEXT Description

marketplace_analysis_cache — Cache du marketplace

Cache d'analyse de compatibilité des skills du marketplace.

ephemeral_tokens — Tokens éphémères (issue #27)

Store persistant pour les tokens OAuth à usage unique : state, réinitialisation de mot de passe, vérification d'email. Avant la migration 022, ces tokens vivaient dans un Map en mémoire et étaient perdus à chaque redémarrage du master, cassant le login Google/GitHub (invalid_state).

Colonne Type Description
token TEXT PK Hex 32 octets (randomBytes(32))
type TEXT NOT NULL oauth_state | password_reset | email_verification
payload TEXT NOT NULL JSON : {provider} pour oauth_state, {email} pour le reste
expires_at INTEGER NOT NULL ms epoch — TTL : 10 min (oauth) / 30 min (reset) / 24h (verify)

INDEX(type), INDEX(expires_at). Usage unique : consume() lit le payload et supprime la ligne dans une seule transaction.

project_issues — Issues par projet (issue #53, Phase 53.14)

SQLite SSOT pour les issues en remplacement du fichier issues/issues.json par projet. Le fichier reste sur le disque comme export dérivé (mirror), gitignored — la DB fait référence.

Colonne Type Description
project_name TEXT NOT NULL Nom du registry ; composante de la PK avec id
id INTEGER NOT NULL Séquence 1-based par projet (max+1 à l'insertion)
title TEXT NOT NULL Titre de l'issue
body TEXT NOT NULL DEFAULT '' Description, Markdown
priority TEXT NOT NULL DEFAULT 'P2' P0 | P1 | P2 | P3
labels TEXT NOT NULL DEFAULT '[]' Tableau JSON de chaînes
status TEXT NOT NULL DEFAULT 'open' open | closed
created_at TEXT NOT NULL Timestamp ISO
updated_at TEXT NOT NULL Timestamp ISO
closed_at TEXT NULL Timestamp ISO quand status=closed
activity TEXT NOT NULL DEFAULT '[]' Tableau JSON {ts, type, author, text} — audit append-only

PK (project_name, id) — chaque projet a son propre espace d'identifiants indépendant. INDEX (project_name, status) — filtre le plus fréquent (open only). Les opérations sont dans issueQueries (shared/db.ts) : list/get/nextId/insert/upsert/replaceAll/bulkImport. replaceAll — transaction atomique ; bulkImportINSERT OR IGNORE pour un re-seed idempotent.

onboarding_progress — État de la checklist post-wizard (issue #56, Phase 54.1)

Cache dérivé par utilisateur sur le flux d'événements activity_log (SSOT). L'UI lit en une seule requête plutôt qu'en agrégeant les événements. Rejouable : dismiss/replay ne modifient pas state, seulement dismissed_at.

Colonne Type Description
chat_id TEXT PRIMARY KEY id utilisateur (registry chat id)
state TEXT NOT NULL DEFAULT '{}' JSON {workers, cli, skill, bot, issue}completed | skipped (clés absentes = pending)
completed_count INTEGER NOT NULL DEFAULT 0 Nombre dérivé de steps completed (0–5)
started_at TEXT NOT NULL DEFAULT (datetime('now')) Création de la ligne (premier événement/dismiss/replay)
completed_at TEXT NULL Timestamp ISO quand les 5 steps sont completed
dismissed_at TEXT NULL Timestamp ISO quand l'utilisateur a fermé la checklist
source TEXT NOT NULL DEFAULT 'web' web | cli — pour l'attribution funnel (#58 arc tour)
updated_at TEXT NOT NULL DEFAULT (datetime('now')) Timestamp de mutation

INDEX sur completed_at pour l'analyse funnel (#61 Phase 54.6). Les opérations sont dans onboardingQueries (shared/db.ts) : getProgress/recordEvent/dismiss/replay. recordEvent est idempotent sur (chat_id, step, status) — un appel identique répété retourne changed=false sans écrire dans activity_log. Whitelist : 5 steps × 2 statuses (completed, skipped). skipped n'incrémente PAS completed_count. La transition skipped → completed ajoute au count. Toutes les mutations émettent des événements dans activity_log avec event_type LIKE 'onboarding_%' — véritable SSOT pour les métriques funnel ; les colonnes de la table sont un cache dérivé.

platform_audit_log — Trail de rotation des secrets super-admin (Phase 57, Sentinel #103)

Journal append-only des mutations sur les secrets de la plateforme dans le vault via l'UI Platform Settings. SSOT pour la forensique post-incident ("quel admin a fait tourner la clé Anthropic à 03:14 UTC ?"). Le vault.json lui-même n'écrit pas ça — value-only.

Colonne Type Description
id INTEGER PRIMARY KEY AUTOINCREMENT Ordre temporel même en cas de clock skew
ts TEXT NOT NULL DEFAULT (datetime('now')) UTC côté serveur ; un attaquant ne peut pas backdater
user_chat_id TEXT NOT NULL Admin qui a agi
user_email TEXT NULL Snapshot de la table users au moment de l'action
action TEXT NOT NULL list | view | rotate | test | restart
key_name TEXT NOT NULL Nom de l'entrée vault (allowlist dans platform.ts) ; * pour list-all
ip TEXT NULL Depuis CF-Connecting-IP / X-Real-IP / XFF tail
result TEXT NOT NULL success ou fail:<reason>
user_agent TEXT NULL Chaîne UA, tronquée à 200 chars

INDEXES : idx_platform_audit_ts (récent toutes clés confondues), idx_platform_audit_user (trail par admin), idx_platform_audit_key (historique de rotation par clé). Invariant append-only : aucun handler UPDATE/DELETE ; l'UI expose en lecture seule recent() + lastRotated() via platformAuditQueries dans shared/db.ts. Chaque action (succès + échec) écrit une ligne.

Migrations

# Nom Phase Ce qui est ajouté
001 initial_schema Phase 40 users, projects, account_settings
002 project_protocol Phase 40.7 projects.project_protocol
003 chat_messages Phase 40.10 chat_messages
004 skill_system Phase 41 skills_global, skills_project_forks, skill_evolution_logs, skill_update_requests
005 skill_benchmarks Phase 40.11d skill_benchmarks
006 system_configs system_configs + FIGMA_CACHE_ROOT
007 marketplace_cache Phase 40.12.1 marketplace_analysis_cache
008 harvester Phase 40.13 +is_suggestion, +source_query, +auto_harvest
009 pinned_notes Phase 41.8 pinned_notes
010 project_phases Phase 44 project_phases
011 activity_log Phase 44.1 activity_log
012 performance_indexes Perf Audit +idx_activity_log_actor, +idx_chat_msg_role_ts
013 timeline_timestamps Phase 47 +timestamp sur les messages worker
014 timeline_events Phase 47 table timeline_events
015 e2ee_chat Phase 45.3 +colonne encrypted sur chat_messages
016 recovery_keys Phase 45.4 table recovery_keys
017 github_links Phase 49.3 table github_links (webhook bindings)
018 github_events Phase 49.3.1 table github_events (event log pour le feed UI)
019 trial_credits Phase 50.1 +users.trial_granted_at, +projects.trial_mode, +projects.trial_tokens_remaining
020 subscriptions Phase 51 table subscriptions (plan, stripe_customer_id, status, feature_flags) + log d'idempotence stripe_events
021 invites Phase 52.1 table invites (enrollment bêta fermée) — code, created_by, used_by, parent_invite_code, status, note
022 ephemeral_tokens issue #27 table ephemeral_tokens — OAuth state persistant / réinitialisation mot de passe / vérification email, en remplacement du Map en mémoire (fix invalid_state au redémarrage du master)
023 project_issues Phase 53.14 / issue #53 table project_issues (PK composite project_name+id, labels/activity JSON) — migre le stockage des issues de issues/issues.json vers SQLite SSOT, le JSON devient un mirror dérivé ; élimine la dérive text-merge au déploiement
024 context_exports Phase 56.4 + 56.5 / issues #100 + #101 export_audit_log (id PK, project_name, owner_id, exported_at, sections JSON, findings_critical/high/medium/low INT, bytes) + export_preferences (project_name PK, always_include_emails BOOL, auto_redact_critical BOOL DEFAULT 1, notify_on_export BOOL DEFAULT 0, updated_at) — historique d'audit par projet pour Project Context Export + bascules de politique par projet ; alerte si ≥3 exports/24h quand notify_on_export = true
025 onboarding_progress Phase 54.1 / issue #56 onboarding_progress (chat_id PK, état JSON pour 5 steps, completed_count dérivé, started_at/completed_at/dismissed_at, source web|cli) — cache dérivé sur le flux d'événements activity_log ; SSOT = événements event_type LIKE 'onboarding_%' ; recordEvent idempotent, dismiss/replay non-destructifs
026 platform_audit_log Phase 57 / Sentinel #103 platform_audit_log (id PK AUTOINCREMENT, ts, user_chat_id, user_email, action, key_name, ip, result, user_agent) — trail d'audit append-only pour les mutations super-admin sur les secrets vault via l'UI Platform Settings. 3 indexes : idx_platform_audit_ts/user/key. Pas d'UPDATE/DELETE — lecture seule via platformAuditQueries.recent + lastRotated
027 auth_events Issue #130 auth_events (id PK AUTOINCREMENT, ts, event, provider, user_id, email, ip, user_agent, result, meta JSON) — Journal d'événements auth + IP append-only. Événements : signup/login/oauth_login/magic_link/device_code_approve. Résultats : success/failed/rate_limited/invalid_invite/already_registered/2fa_required/invalid_token. 4 index : ts/user_id/ip/event+result
028 managed_containers Phase 60 #135/#136 managed_containers (id TEXT PK (nom du conteneur Docker), user_id, status (provisioning|ready|paused|suspended|deleted), server_ip, internal_port INT, claude_authed INT, github_authed INT, created_at, last_active) — Registre de conteneurs Docker 1-par-utilisateur pour Standard Cloud. managedContainerQueries.findByUser/insert/updateStatus/setAuthFlag/touch/listActive dans shared/db.ts. Endpoints : POST /api/crm/cloud/provision, GET /status, POST /deprovision dans shared/routes/cloud.ts
029 cloud_waitlist Phase 60 #134 cloud_waitlist (id INT PK AUTOINCREMENT, user_id TEXT UNIQUE, email, position INT, status (waiting|invited|activated|declined), notes, joined_at, invited_at, activated_at) — Liste d'attente Early Access pour Standard Cloud. cloudWaitlistQueries.join/findByUser/list/invite/activate/decline/stats dans shared/db.ts. Endpoints : POST /api/crm/cloud/waitlist (rejoindre), GET /waitlist/status (propre statut), GET /waitlist (liste admin), POST /waitlist/invite (admin active + upgrade plan vers cloud)
030 translation_feedback Phase 59.4 #122 translation_feedback (id INT PK AUTOINCREMENT, locale TEXT, message_id TEXT, original TEXT, current_translation TEXT, suggestion TEXT, user_id TEXT, status (pending|accepted|rejected), created_at, reviewed_at) — Feedback qualité de traduction par locale. Tableau de bord de révision admin sur /api/crm/translations/feedback (list/stats/accept/reject).
031 token_usage_log Phase 63 #148 token_usage_log (id INT PK AUTOINCREMENT, project_name TEXT NOT NULL, owner_id TEXT NOT NULL, worker_id TEXT, input_tokens INT DEFAULT 0, output_tokens INT DEFAULT 0, cache_tokens INT DEFAULT 0, total_tokens INT DEFAULT 0, created_at INT DEFAULT (unixepoch())) — Journal d'utilisation des tokens Claude par requête. 3 index : owner_id, project_name, created_at. Rempli par child-bot/claude-runner.ts via POST /api/internal/usage/log (fire-and-forget). Interrogé par GET /api/crm/account/usage → retourne { rows: [...200 derniers], totals: { total, input, output } } pour BillingPage + UserDropdown UsageCard.
032 arc_help_usage Phase 61 #147 arc_help_usage (id INT PK AUTOINCREMENT, user_id TEXT NOT NULL, date TEXT NOT NULL (YYYY-MM-DD), count INT NOT NULL DEFAULT 0, PRIMARY KEY (user_id, date)) — Compteur de limite de débit quotidien pour le chat Aide IA intégré (30 messages/jour). Interrogé par GET /api/crm/help/usage ; mis à jour sur POST /api/crm/help/chat.

Relations (Foreign Keys)

Tout en CASCADE — supprimer un skill supprime tous ses forks, son historique, ses PRs et ses benchmarks.

Diagramme ER (textuel)

users ─1:N──> projects
            ├── chat_messages
            ├── pinned_notes
            ├── project_phases
            └── skills_project_forks ──> skills_global
                                          ├── skill_evolution_logs
                                          ├── skill_update_requests
                                          │    └── skill_benchmarks
                                          └── (tables autonomes)
                                               ├── activity_log
                                               ├── system_configs
                                               └── marketplace_analysis_cache