Base de données — Schéma SQLite
Informations générales
- Fichier :
data/citadel.db - Mode : WAL (Write-Ahead Logging) — lecture parallèle
- Foreign keys : activées
- Busy timeout : 5000ms
- Cache : 64MB
- Migrations :
shared/migrations/001-044
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 |
| 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 ; bulkImport — INSERT 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é |
| 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)
- skills_project_forks.skill_id → skills_global.id (CASCADE DELETE)
- skill_evolution_logs.skill_id → skills_global.id (CASCADE DELETE)
- skill_update_requests.skill_id → skills_global.id (CASCADE DELETE)
- skill_benchmarks.request_id → skill_update_requests.id (CASCADE DELETE)
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). |