Architecture du backend CRM

Phase 22.0–48. Statut : DÉPLOYÉ (VPS en ligne, 68+ endpoints, architecture modulaire). Dernière mise à jour : 2026-04-28 (Phase 48 — Décomposition architecture)

Vue d'ensemble

Navigateur (:18888)
  │
  ├── Fichiers statiques ──► Nginx ──► Docker frontend (React + Nginx)
  │
  ├── /api/crm/* ────► Nginx ──► Bun Master Bot (:19210)
  │                                 ├── crmAuthMiddleware (HMAC-SHA256)
  │                                 └── shared/routes/router.ts → 17 modules domaine
  │
  ├── /api/auth/* ───► Nginx ──► Bun (:19210) → master-bot/routes/auth.ts
  ├── /api/cli/*  ───► Nginx ──► Bun (:19210) → master-bot/routes/cli.ts
  ├── /api/internal/* ► Bun (:19210, loopback uniquement) → master-bot/routes/internal.ts
  │
  ├── /api/sse/* ────► Nginx (non bufférisé) ──► Bun (:19210)
  │
  └── /ws/terminal/* ► Nginx (upgrade) ──► Bun (:19210) → master-bot/routes/websocket.ts

Structure des modules (Phase 48)

Serveur API (master-bot/api-server.ts, 196 lignes) — dispatcher léger :

Routes CRM (shared/routes/router.ts, 512 lignes) — table de dispatch pour 17 modules domaine :

Flux d'authentification

Utilisateur ──► LoginOverlay (frontend)
         │
         ├── Email/mot de passe → POST /api/auth/login
         └── OAuth → /api/auth/{google,github} → callback
         │
         ▼
    Auth réussie → token HMAC-SHA256
         │
         ├── payload = { sub: userId, iat, exp: +24h }
         ├── signature = HMAC-SHA256(base64url(payload), CRM_SECRET)
         └── token = base64url(payload) + "." + signature
         │
         ▼
    localStorage('crm-token') → Tous les appels API : Authorization: Bearer <token>
         │
         ▼
    Token expiré → 401 → reconnexion requise

Fichiers clés

Fichier Rôle
shared/auth.ts Génération/vérification de token, email/OAuth, réinitialisation mot de passe, vérification email, CORS, middleware
shared/vault.ts Stockage de secrets AES-256-GCM (CRM_SECRET auto-créé)
frontend/src/crm/components/LoginOverlay.jsx UI de connexion (email, OAuth, réinitialisation mot de passe, vérification email)

Propriétés de sécurité

Routes API CRM

Toutes les routes sous /api/crm/ requièrent Authorization: Bearer <token>.

GET /api/crm/projects

Liste tous les projets enregistrés avec statut de santé en direct. Inclut master comme pseudo-projet (DEBT-6).

Champ Source
name, type, bot_username config/bot_registry.json (master : entrée virtuelle, type: "system")
healthy Vérification santé HTTP (enfants : port child, master : /api/master/health)
tmux_alive tmux has-session -t <name> (master : citadel-master)
status Fichier heartbeat ou dérivé de health+tmux (master : healthy/degraded/down)

Master est toujours ajouté en première entrée dans le tableau de réponse.

GET /api/crm/projects/:name

Fiche détaillée complète du projet.

Champ Source
manifest Texte brut depuis <cwd>/MANIFEST.md
heartbeat JSON depuis state/heartbeat_<name>.json
skills[] Noms de fichiers <cwd>/skills/*.md (excluant _*.md)
healthy, tmux_alive Même que l'endpoint liste

GET /api/crm/projects/:name/logs

Tail des logs JSONL structurés.

Paramètre Défaut Max
lines 100 500
category dialog system, dialog, error

Source : /var/log/citadel/<name>/<category>-YYYY-MM-DD.log

Lit les fichiers de log les plus récents en premier, parse le JSONL, retourne en ordre chronologique.

GET /api/crm/projects/:name/files

Listing de répertoire avec protection contre le path traversal.

Paramètre Défaut Description
path / Chemin relatif dans le cwd du projet

Sécurité : resolve(base, requested) doit startsWith(resolve(base)). Violation → 403.

Entrées de réponse : { name, type: "file"|"directory", size?, modified }, triées répertoires en premier.

GET /api/crm/projects/:name/skills

Skills installés avec attribution de source.

Source Détection
file <cwd>/skills/*.md (excluant _*.md)
manifest MANIFEST.mdskills[] + library_skills[] (parse JSON)

Dédupliqué : la source fichier a priorité sur le manifest.

POST /api/crm/projects/:name/restart (Phase 22.3)

Redémarre un child bot via le watchdog.

Aspect Détail
Méthode POST uniquement (GET retourne 405)
Auth Token HMAC-SHA256 (même que les autres routes CRM)
Cooldown 30 secondes par projet (retourne 429 si trop fréquent)
Mécanisme Appelle restartChild() depuis watchdog.ts
Réponse { ok: true } ou { error: "..." }

GET /api/crm/projects/:name/metrics (Phase 22.1)

Séries temporelles de métriques qualité pour les graphiques sparkline.

Paramètre Défaut Description
days 7 Nombre de jours à agréger

Source : /var/log/citadel/<name>/quality-events.log → décomptes succès/échecs par jour.

WS /ws/terminal/:name (Phase 22.3)

Streaming de contenu de pane tmux via WebSocket.

Aspect Détail
Auth Paramètre query ?token= (vérifié via verifyToken().valid)
Mode Lecture seule par défaut ; ?mode=interactive active l'envoi de touches
Polling tmux capture-pane -p -e -t {session} -S -80 toutes les 200ms
Delta Envoie uniquement si le contenu change par rapport au précédent

Sécurité : Protection contre le path traversal (Phase 22.3, DEBT-7)

Tous les endpoints valident les noms de projet avant traitement :

const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
export function isValidProjectName(name: string): boolean {
  return name.length > 0 && name.length <= 64 && SAFE_NAME_RE.test(name);
}

Appliqué à 3 points d'entrée : routeCrmRequest(), routeSseRequest(), handler d'upgrade WebSocket dans bot.ts. Noms invalides → 403 Forbidden.

Intégrité des données : Écritures atomiques (Phase 22.3, DEBT-2)

Les écritures de registry dans onboarding.ts utilisent le pattern atomique :

async function atomicWriteJson(filePath: string, data: unknown): Promise<void> {
  const tmp = `${filePath}.tmp.${process.pid}`;
  await Bun.write(tmp, JSON.stringify(data, null, 2));
  Bun.spawn(["mv", tmp, filePath]); // renommage atomique POSIX
}

Fichiers clés

Fichier Rôle Lignes
shared/crm-routes.ts 55+ handlers + routeur routeCrmRequest() + isValidProjectName() ~4800
shared/sage.ts Sage Worker : runSageAnalysis(), runBenchmark(), juge LLM, tests A/B ~550
shared/db.ts SQLite SSOT : userQueries, projectQueries, skillQueries, benchmarkQueries, chatQueries ~600
shared/auth.ts Génération token, vérification, CORS, middleware ~100
master-bot/bot.ts Bun.serve : routeur HTTP + handler terminal WebSocket ~1160
master-bot/onboarding.ts atomicWriteJson() pour les écritures de registry ~850

Proxy Nginx

Fichier : infra/nginx/citadel-crm.conf — remplace la config legacy citadel-os.

Trois upstreams :

Upstream Port Service
crm_backend 19210 Bun Master Bot (CRM API, santé)
phaser_frontend 18889 Docker Phaser office (legacy)
mcp_bridge 19200 Docker MCP state-bridge (legacy)
Location Backend Auth Config spéciale
/ phaser_frontend auth basique Docker Phaser frontend
/api/crm/ crm_backend token (sans basique) Timeout 30s, keepalive
/api/master/health crm_backend aucune Vérification santé publique
/api/sse/ crm_backend token (sans basique) proxy_buffering off, timeout 1h (Phase 22.1)
/ws/ crm_backend token (sans basique) Upgrade WebSocket, timeout 1h (Phase 22.1)
/api/ mcp_bridge aucune (sans basique) API d'état legacy + SSE
/health mcp_bridge aucune Santé bridge legacy

Tous les emplacements transmettent X-Real-IP, X-Forwarded-For, X-Forwarded-Proto.

Chemins bloqués : /.* (dotfiles), /config/, /state/, /scripts/.

Note : La priorité des locations nginx assure que /api/crm/ et /api/sse/ correspondent avant le catch-all /api/ (MCP legacy).

Intégration dans le Master Bot

master-bot/bot.ts Bun.serve (:19210) :

async fetch(req) {
  /api/master/health  →  public, sans auth
  /api/crm/*          →  preflight CORS → crmAuthMiddleware → routeCrmRequest()
  *                   →  404
}

Le routeur dispatche vers les fonctions handler de shared/crm-routes.ts. Les headers CORS sont injectés sur chaque réponse CRM (y compris les erreurs 401).

Résumé des endpoints (toutes phases)

Phase Fonctionnalité Endpoint Statut
22.0 Liste projets GET /api/crm/projects DONE
22.0 Détail projet GET /api/crm/projects/:name DONE
22.0 Tail de logs GET /api/crm/projects/:name/logs DONE
22.0 Listing fichiers GET /api/crm/projects/:name/files DONE
22.0 Listing skills GET /api/crm/projects/:name/skills DONE
22.1 Streaming SSE logs GET /api/sse/logs/:name DONE
22.1 Historique métriques GET /api/crm/projects/:name/metrics DONE
22.3 Redémarrage projet POST /api/crm/projects/:name/restart DONE
22.3 Terminal WebSocket WS /ws/terminal/:name DONE
24.5 CRUD specs GET/POST /api/crm/projects/:name/specs DONE
24.5 Rôle actif GET/POST /api/crm/projects/:name/active-role DONE
25 Bundle skills GET /api/crm/projects/:name/skills-bundle DONE
25 Sync learnings GET/POST /api/crm/projects/:name/learnings DONE
26 CRUD workers GET/POST /api/crm/projects/:name/workers DONE
32 Sauvegarde wiki PUT /api/crm/projects/:name/wiki/save DONE
32 Sauvegarde/suppression skills PUT/DELETE /api/crm/projects/:name/skills/* DONE
33 Paramètres compte GET/PUT /api/crm/account/settings DONE
33 Créer projet POST /api/crm/projects/create DONE
34 CRUD tickets POST/GET/PUT /api/mcp/issues/:project DONE
34 Wiki MCP PUT /api/mcp/wiki/:project DONE
34 Sync roadmap GET/PUT /api/mcp/roadmap/:project DONE
34 Init CLI GET /api/cli/init/:project/:mode DONE
35 Ingestion log terminal POST /api/crm/projects/:name/terminal/log DONE
36 Chat Cloud PM POST /api/crm/projects/:name/chat DONE
36.6 Génération skill POST /api/crm/projects/:name/skills/generate DONE
36.7 Listing notebooks GET /api/crm/projects/:name/notebooks DONE

| 40.14 | Roadmap (contenu) | GET /api/mcp/roadmap/:project (+ champ content) | DONE |

Total : 46+ endpoints (REST + SSE + WebSocket + CLI/MCP)

NotebookLM Bridge (Phase 36.3)

Service Python FastAPI séparé sur :19213 (localhost uniquement). Pas partie de l'API CRM Bun.

Endpoint Méthode Objectif
/query POST Recherche sémantique via Google NotebookLM
/sync POST Mettre en file une source pour sync async (fire-and-forget)
/notebooks/init POST Créer un notebook pour un nouveau projet
/health GET Statut auth, nombre de notebooks, stats de file d'attente

Le CRM Bun déclenche des appels fire-and-forget vers le bridge lors de :

Dette technique

Voir docs/backlog/technical-debt.md pour le registre complet. Éléments clés :

ID Titre Statut
DEBT-2 Écritures JSON atomiques RÉSOLU (Phase 22.3)
DEBT-3 Token Docker intégré → auth session RÉSOLU (Phase 31)
DEBT-6 Angle mort CRM master bot RÉSOLU (Phase 22.3)
DEBT-7 Défense contre path traversal RÉSOLU (Phase 22.3)

Maintenu par Rick (Orchestrateur). Mis à jour après chaque phase terminée.