Base de données — Schéma SQLite

Informations générales

Diagramme ER (Phase 65)

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
        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)
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' draft | open | in_progress | blocked | deferred | closed (le DEFAULT SQL 'open' est conservé — les nouvelles issues sont forcées en draft au niveau applicatif)
assignee TEXT NULL worker id qui a pris l'issue (migration 036)
created_by TEXT NOT NULL DEFAULT 'legacy' worker id, qui a créé l'issue (migration 046 / #327) — pour l'audit (qui, quand, pour qui)
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.

Migration 036 (#187, 2026-05-23) : ALTER TABLE project_issues ADD COLUMN assignee TEXT. Les nouveaux statuts (in_progress, blocked, deferred) sont validés au niveau applicatif (union IssueStatus dans shared/db.ts), pas par une contrainte CHECK (SQLite ne permet pas d'ajouter une contrainte via ALTER TABLE).

Migration 046 (#327, 2026-06-03) : ALTER TABLE project_issues ADD COLUMN created_by TEXT NOT NULL DEFAULT 'legacy'. Le cycle de vie est étendu avec le nouveau statut draft. handleCreateIssue force status='draft' quand assignee=null (sinon directement 'open'), exige worker_id (body ou fallback auth). handleUpdateIssue auto-promeut draft → open à la première affectation d'un assignee et rejette close pour une issue jamais assignée (FR-ISS-101/102/103). Les lignes préexistantes sont backfillées via DEFAULT en 'legacy'.

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.

auth_events — Journal des événements d'authentification end-user (Migration 027, issue #130)

Journal append-only des événements d'auth pour l'audit de sécurité et l'investigation d'incidents. Distinct de platform_audit_log (vault admin-only) — cette table couvre le flux d'auth end-user avec IP.

Colonne Type Description
id INTEGER PRIMARY KEY AUTOINCREMENT Ordre temporel
ts TEXT NOT NULL DEFAULT (datetime('now')) UTC côté serveur
event TEXT NOT NULL signup | login | oauth_login | magic_link | device_code_approve
provider TEXT NOT NULL DEFAULT 'password' password | google | github | magic_link | device_code
user_id TEXT NULL NULL pour les tentatives échouées où l'utilisateur n'est pas identifié
email TEXT NULL Email utilisé dans la tentative
ip TEXT NOT NULL DEFAULT 'unknown' CF-Connecting-IP → X-Real-IP → dernier segment XFF
user_agent TEXT NULL Chaîne UA
result TEXT NOT NULL success | failed | rate_limited | invalid_invite | already_registered | 2fa_required | invalid_token
meta TEXT NULL Contexte JSON supplémentaire (p. ex. {isNew: true} pour OAuth)

INDEXES : idx_auth_events_ts (plus récent en premier), idx_auth_events_user (historique par utilisateur), idx_auth_events_ip (détection de menaces par IP), idx_auth_events_event (filtre event+result). authEventQueries.insert/recent dans shared/db.ts. Loggé dans master-bot/routes/auth.ts (register/login/oauth/magic-link) et master-bot/routes/cli.ts (device_code_approve).

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.
033 arc_help_messages Phase 61 #153 arc_help_messages (id PK AUTOINCREMENT, user_id, role, text, sources JSON, created_at) — Historique persistant du chat Arc Help par utilisateur (60 derniers).
034 skills_owner_project #157 ALTER TABLE skills_global ADD COLUMN owner_project TEXT — NULL=global, non-null=détenu par un projet.
035 password_version Phase 64 #174 ALTER TABLE users ADD COLUMN password_version INTEGER DEFAULT 0 — incrémenté au changement de mot de passe ; le claim JWT pv est validé par crmAuthMiddleware pour invalider les anciens tokens.
036 issue_assignee #187 / 2026-05-23 ALTER TABLE project_issues ADD COLUMN assignee TEXT — worker id nullable. Active arc issue take <id> + les statuts étendus in_progress/blocked/deferred (validés au niveau applicatif).
046 issue_draft_and_created_by #327 / 2026-06-03 ALTER TABLE project_issues ADD COLUMN created_by TEXT NOT NULL DEFAULT 'legacy' — champ d'audit "qui a créé". Cycle de vie += draft (union au niveau applicatif). handleCreateIssue force draft sans assignee ; handleUpdateIssue auto-promeut draft→open à l'assignation + rejette close pour les issues jamais assignées (FR-ISS-101/102/103).
037 waitlist #197 / 2026-05-25 waitlist (id INT PK AUTOINCREMENT, email TEXT UNIQUE, message, status DEFAULT 'pending', created_at, updated_at) — formulaire de demande d'accès anticipé sur la page de connexion.
038 plata_billing Phase 65 #202 / 2026-05-26 Ajoute les colonnes Plata by mono à subscriptions : plata_card_token TEXT, plata_wallet_id TEXT, plata_masked_pan TEXT, next_billing_date TEXT, billing_failures INTEGER DEFAULT 0. Crée aussi la table d'idempotence plata_events (invoice_id PK, status, processed_at).
039 skill_project_grants #201 / 2026-05-26 skill_project_grants (skill_id INT FK→skills_global.id CASCADE, project_name TEXT, granted_at, PK composite) — accès many-to-many aux skills entre projets.
040 drop_stripe #205 / 2026-05-26 DROP de la colonne stripe_customer_id + DROP TABLE stripe_events. NB : la colonne stripe_subscription_id a survécu à cette migration car SQLite refuse DROP COLUMN sur les colonnes avec contrainte UNIQUE (silencieusement attrapé par try/catch). Voir migration 041.
041 rebuild_subscriptions #205 / 2026-05-26 Suite de la 040 — reconstruit la table subscriptions sans la colonne orpheline stripe_subscription_id via le pattern CREATE TABLE … AS. Idempotente (sautée quand la colonne est déjà absente).
042 totp_backup_codes ALTER TABLE users ADD COLUMN totp_backup_codes TEXT — codes de récupération TOTP.
043 worker_avatars Phase D #304 / 2026-05-30 worker_avatars (id PK AUTOINCREMENT, project_name, worker_id, file_path, mime_type, created_at ; UNIQUE(project_name, worker_id)) — stockage des métadonnées des avatars worker uploadés. Fichiers enregistrés dans data/worker-avatars/. Servi via GET /api/crm/projects/:name/workers/:id/avatar.
044 worker_templates Phase I #304 / 2026-05-30 worker_templates (id PK AUTOINCREMENT, owner_id, name, description, config JSON, is_public, created_at ; UNIQUE(owner_id, name)) — templates de workers enregistrés par l'utilisateur. Endpoints : GET/POST /api/crm/workers/templates, DELETE /api/crm/workers/templates/:id.
045 workers_runtime_state Phase F #306 / 2026-05-31 workers_runtime_state (project_name, worker_id, status, status_started_at, current_skill, current_task_id, updated_at ; PK(project_name, worker_id), idx sur updated_at) — état runtime live par worker. Écrit par handleSetActiveRole (invariant : un seul rôle actif par projet). Lu par GET /api/crm/projects/:name/workers/:id/runtime avec fallback de péremption : status='working' + tmux mort + updated_at > 10 min → forcé à idle (détection de crash).
049 embeddings Phase 71.2 #359 / 2026-06-04 embeddings (id PK AUTOINCREMENT, project, doc_type wiki/issue/skill, doc_id, chunk_ix, text, created_at) + embeddings_vec (table virtuelle vec0, embedding FLOAT[1024]). Séparation : vec0 ne peut pas stocker les métadonnées efficacement ; les workflows LIST/DELETE tournent en SQL classique. L'extension sqlite-vec est auto-chargée dans initDb avant l'exécution de la migration.
050 drop_notebook_id Phase 71.8 #365 / 2026-06-05 ALTER TABLE projects DROP COLUMN notebook_id. Décommissionne le champ de schéma du bridge NotebookLM ; la recherche sémantique passe désormais par le RAG self-hosted (Cohere + sqlite-vec). Idempotente via un check PRAGMA table_info pour que les DB fraîches avec le schéma 001 actuel ne trébuchent pas sur la colonne manquante.
051 voice_usage Phase 62.4 #373 / 2026-06-05 voice_usage_log (user_id TEXT, date TEXT YYYY-MM-DD, seconds_used INTEGER DEFAULT 0, PRIMARY KEY(user_id, date)) — compteur de quota quotidien de transcription vocale, reprend la forme de la migration 032 arc_help_usage. Secondes approximatives (estimées à partir de la taille de l'upload en supposant un codec voix ~32 kbps) ; comparées au plafond souple de 60 min/jour dans handleVoiceTranscribe (shared/routes/voice.ts) avant chaque transcription pour empêcher qu'un utilisateur monopolise le pool de workers whisper-server partagé sur Contabo.
052 transcripts Phase 73.1 #377 / 2026-06-05 transcripts (id PK AUTOINCREMENT, project TEXT, owner_id TEXT, source_filename TEXT, source_bytes INTEGER, source_kind TEXT CHECK(audio|video), duration_seconds INTEGER, transcript_text TEXT, frames_json TEXT, summary_json TEXT, embed_to_rag INTEGER DEFAULT 1, status TEXT DEFAULT 'queued', error TEXT, created_at TEXT DEFAULT datetime('now'), completed_at TEXT) — une ligne par fichier de réunion uploadé. frames_json = tableau JSON de {ts_ms, description} issu de Claude vision (Phase 73.4). summary_json = {tldr, key_points, action_items, decisions, topics, model, generated_at} issu de Claude Sonnet (Phase 73.5). Le fichier source est supprimé du disque dès que status='done' (décision CEO D4). Index sur (project, created_at DESC) et (owner_id, created_at DESC).
056 skill_usage #319 / 2026-06-07 skill_usage (skill_name TEXT, project_name TEXT DEFAULT '_global_', trigger_count INTEGER DEFAULT 0, install_count INTEGER DEFAULT 0, session_count INTEGER DEFAULT 0, last_triggered_at TEXT, updated_at TEXT, PRIMARY KEY(skill_name, project_name)) — compteurs cumulés par skill et par projet. trigger_count s'incrémente chaque fois que le context router injecte le skill ; install_count au save/create ; session_count à l'apparition dans une session unique. Utilisé par GET /api/crm/projects/:name/skills/usage.
057 notes Phase 78.1 #395 / 2026-06-07 4 tables : notes (id PK, project_name, title, description, created_by, created_at, updated_at) — la note comme conteneur de sources. note_sources (id PK, note_id FK→notes, source_type CHECK(video|audio|youtube|web|pdf|docx|txt|image), filename, file_path, file_size, url, title, content_text, duration_seconds, status CHECK(queued|processing|done|error), error, processed_at, created_at) — une source par ligne, content_text = texte extrait pour le RAG. note_issue_links (note_id FK, issue_id, project_name, linked_at, PK(note_id,issue_id)) — lien note↔issue. note_chats (id PK, note_id FK→notes, role CHECK(user|assistant), content, created_at) — historique du chat avec la note. Index : notes(project_name,created_at DESC), note_sources(note_id), note_sources(status), note_issue_links(project_name,issue_id), note_chats(note_id,created_at).
053 transcript_jobs Phase 73.1 #377 / 2026-06-05 transcript_jobs (job_id TEXT PK, transcript_id INTEGER, status TEXT DEFAULT 'queued', progress_pct INTEGER DEFAULT 0, step_label TEXT, error TEXT, started_at TEXT DEFAULT datetime('now'), updated_at TEXT DEFAULT datetime('now')) — traqueur de progression à forte rotation pour le worker en arrière-plan. Isolé de transcripts pour que les UPDATE fréquents (toutes les quelques secondes pendant ffmpeg/whisper) ne fassent pas churner les index de la table principale. L'endpoint SSE streame cette table avec un poll à 1 s. Cycle de vie du statut : queued → extracting_audio → transcribing → (vidéo : extracting_frames → frames_extracted → vision_analyzing → vision_analyzed) → summarizing → summarized → embedding → done (ou failed à n'importe quelle étape). Index sur (status, updated_at).

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

| 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 (l'admin active + upgrade le 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 de 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 + carte d'usage du UserDropdown. | | 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 quotidienne 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. | | 033 | arc_help_messages | Phase 61 #153 | arc_help_messages (id INT PK AUTOINCREMENT, user_id TEXT NOT NULL, role TEXT NOT NULL CHECK(user|assistant), text TEXT NOT NULL, sources TEXT (tableau JSON, nullable), created_at TEXT DEFAULT datetime('now')) — Historique persistant du chat Arc Help par utilisateur. INDEX sur (user_id, created_at DESC). arcHelpQueries.save/history(limit=60)/clearHistory dans shared/db.ts. GET /api/crm/help/history retourne les 60 derniers messages (du plus ancien au plus récent). DELETE /api/crm/help/history efface tout pour l'utilisateur. Le frontend charge au montage. | | 034 | skills_owner_project | #157 | ALTER TABLE skills_global ADD COLUMN owner_project TEXT DEFAULT NULL — NULL=global/marketplace (visible par tous), non-null=détenu par ce projet (filtré dans listForProject). Backfill : les skills avec une catégorie non générique (odoo, python, etc.) reçoivent owner_project=catégorie. Catégories génériques : general/frontend/backend/devops/security/testing/database/api/mobile. Les skills DB sont désormais injectés dans les prompts Claude via routeContextFromDb() dans le child-bot (#158). |