CRM Backend Architecture

Phase 22.0–48. Статус: DEPLOYED (VPS live, 68+ эндпоинтов, модульная архитектура). Последнее обновление: 2026-04-28 (Phase 48 — Architecture Decomposition)

Обзор

Browser (:18888)
  │
  ├── Static files ──► Nginx ──► Docker frontend (React + Nginx)
  │
  ├── /api/crm/* ────► Nginx ──► Bun Master Bot (:19210)
  │                                 ├── crmAuthMiddleware (HMAC-SHA256)
  │                                 └── shared/routes/router.ts → 17 доменных модулей
  │
  ├── /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) → master-bot/routes/internal.ts
  │
  ├── /api/sse/* ────► Nginx (unbuffered) ──► Bun (:19210)
  │
  └── /ws/terminal/* ► Nginx (upgrade) ──► Bun (:19210) → master-bot/routes/websocket.ts

Структура модулей (Phase 48)

API Server (master-bot/api-server.ts, 196 строк) — тонкий диспетчер:

CRM Routes (shared/routes/router.ts, 512 строк) — таблица диспетчеризации для 17 доменных модулей:

Поток аутентификации

Пользователь ──► LoginOverlay (фронтенд)
                 │
                 ├── Email/password → POST /api/auth/login
                 └── OAuth → /api/auth/{google,github} → callback
                 │
                 ▼
    Успешная аутентификация → HMAC-SHA256 токен
                 │
                 ├── payload = { sub: userId, iat, exp: +24h }
                 ├── signature = HMAC-SHA256(base64url(payload), CRM_SECRET)
                 └── token = base64url(payload) + "." + signature
                 │
                 ▼
    localStorage('crm-token') → Все API-вызовы: Authorization: Bearer <token>
                 │
                 ▼
    Токен истёк → 401 → требуется вход

Ключевые файлы

Файл Роль
shared/auth.ts Генерация/верификация токена, email/OAuth, сброс пароля, верификация email, CORS, middleware
shared/vault.ts Хранилище секретов AES-256-GCM (CRM_SECRET создаётся автоматически)
frontend/src/crm/components/LoginOverlay.jsx UI входа (email, OAuth, сброс пароля, верификация email)

Свойства безопасности

CRM API Routes

Все маршруты под /api/crm/ требуют Authorization: Bearer <token>.

GET /api/crm/projects

Список всех зарегистрированных проектов с live статусом health. Включает master как псевдопроект (DEBT-6).

Поле Источник
name, type, bot_username config/bot_registry.json (master: виртуальная запись, type: "system")
healthy HTTP health check (дочерние: child port, master: /api/master/health)
tmux_alive tmux has-session -t <name> (master: citadel-master)
status Файл heartbeat или производное из health+tmux (master: healthy/degraded/down)

Master всегда добавляется первым элементом в массиве ответа.

GET /api/crm/projects/:name

Полная карточка деталей проекта.

Поле Источник
manifest Текст из <cwd>/MANIFEST.md
heartbeat JSON из state/heartbeat_<name>.json
skills[] Имена файлов <cwd>/skills/*.md (исключая _*.md)
healthy, tmux_alive Как в list-эндпоинте

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

Tail JSONL-структурированных логов.

Параметр По умолчанию Максимум
lines 100 500
category dialog system, dialog, error

Источник: /var/log/citadel/<name>/<category>-YYYY-MM-DD.log

Читает самые свежие файлы логов первыми, парсит JSONL, возвращает в хронологическом порядке.

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

Листинг директории с защитой от path traversal.

Параметр По умолчанию Описание
path / Относительный путь внутри cwd проекта

Безопасность: resolve(base, requested) должен startsWith(resolve(base)). Нарушение → 403.

Элементы ответа: { name, type: "file"|"directory", size?, modified }, отсортированы: директории первыми.

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

Установленные скиллы с атрибуцией источника.

Источник Определение
file <cwd>/skills/*.md (исключая _*.md)
manifest MANIFEST.mdskills[] + library_skills[] (JSON parse)

Дедуплицировано: источник file имеет приоритет над manifest.

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

Перезапуск дочернего бота через watchdog.

Аспект Деталь
Метод Только POST (GET возвращает 405)
Auth HMAC-SHA256 токен (аналогично другим CRM-маршрутам)
Кулдаун 30 секунд на проект (возвращает 429 при слишком частых запросах)
Механизм Вызывает restartChild() из watchdog.ts
Ответ { ok: true } или { error: "..." }

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

Временной ряд метрик качества для sparkline-графиков.

Параметр По умолчанию Описание
days 7 Количество дней для агрегации

Источник: /var/log/citadel/<name>/quality-events.log → подсчёт успехов/ошибок по дням.

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

WebSocket-терминал, транслирующий содержимое tmux-панели.

Аспект Деталь
Auth Query-параметр ?token= (проверяется через verifyToken().valid)
Режим По умолчанию read-only; ?mode=interactive включает send-keys
Опрос tmux capture-pane -p -e -t {session} -S -80 каждые 200мс
Дельта Отправляет только при изменении содержимого относительно предыдущего

Безопасность: защита от Path Traversal (Phase 22.3, DEBT-7)

Все эндпоинты валидируют имена проектов перед обработкой:

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);
}

Применяется в 3 точках входа: routeCrmRequest(), routeSseRequest(), WebSocket upgrade handler в bot.ts. Невалидные имена → 403 Forbidden.

Целостность данных: атомарные записи (Phase 22.3, DEBT-2)

Записи реестра в onboarding.ts используют атомарный паттерн:

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]); // POSIX atomic rename
}

Ключевые файлы

Файл Роль Строки
shared/crm-routes.ts 55+ обработчиков + роутер routeCrmRequest() + isValidProjectName() ~4800
shared/sage.ts Sage Worker: runSageAnalysis(), runBenchmark(), LLM judge, A/B тестирование ~550
shared/db.ts SQLite SSOT: userQueries, projectQueries, skillQueries, benchmarkQueries, chatQueries ~600
shared/auth.ts Генерация токена, верификация, CORS, middleware ~100
master-bot/bot.ts Bun.serve: HTTP роутер + WebSocket терминал-хэндлер ~1160
master-bot/onboarding.ts atomicWriteJson() для записей реестра ~850

Nginx Proxy

Файл: infra/nginx/citadel-crm.conf — заменяет legacy-конфиг citadel-os.

Три upstream-а:

Upstream Порт Сервис
crm_backend 19210 Bun Master Bot (CRM API, health)
phaser_frontend 18889 Docker Phaser office (legacy)
mcp_bridge 19200 Docker MCP state-bridge (legacy)
Location Бэкенд Auth Особая конфигурация
/ phaser_frontend basic auth Docker Phaser frontend
/api/crm/ crm_backend токен (без basic) таймаут 30с, keepalive
/api/master/health crm_backend нет Публичный health check
/api/sse/ crm_backend токен (без basic) proxy_buffering off, таймаут 1ч (Phase 22.1)
/ws/ crm_backend токен (без basic) WebSocket upgrade, таймаут 1ч (Phase 22.1)
/api/ mcp_bridge нет (без basic) Legacy state API + SSE
/health mcp_bridge нет Legacy bridge health

Все location'ы передают X-Real-IP, X-Forwarded-For, X-Forwarded-Proto.

Заблокированные пути: /.* (dotfiles), /config/, /state/, /scripts/.

Примечание: Приоритет location в Nginx гарантирует, что /api/crm/ и /api/sse/ матчатся раньше catch-all /api/ (legacy MCP).

Интеграция в Master Bot

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

async fetch(req) {
  /api/master/health  →  публичный, без auth
  /api/crm/*          →  CORS preflight → crmAuthMiddleware → routeCrmRequest()
  *                   →  404
}

Роутер диспетчеризует к функциям-обработчикам из shared/crm-routes.ts. CORS-заголовки вставляются в каждый CRM-ответ (включая 401-ошибки).

Сводка эндпоинтов (все фазы)

Фаза Фича Эндпоинт Статус
22.0 Список проектов GET /api/crm/projects DONE
22.0 Детали проекта GET /api/crm/projects/:name DONE
22.0 Хвост логов GET /api/crm/projects/:name/logs DONE
22.0 Листинг файлов GET /api/crm/projects/:name/files DONE
22.0 Листинг скиллов GET /api/crm/projects/:name/skills DONE
22.1 SSE log streaming GET /api/sse/logs/:name DONE
22.1 История метрик GET /api/crm/projects/:name/metrics DONE
22.3 Перезапуск проекта POST /api/crm/projects/:name/restart DONE
22.3 WebSocket терминал WS /ws/terminal/:name DONE
24.5 Specs CRUD GET/POST /api/crm/projects/:name/specs DONE
24.5 Активная роль GET/POST /api/crm/projects/:name/active-role DONE
25 Skills bundle GET /api/crm/projects/:name/skills-bundle DONE
25 Синхронизация learnings GET/POST /api/crm/projects/:name/learnings DONE
26 Workers CRUD GET/POST /api/crm/projects/:name/workers DONE
32 Сохранение вики PUT /api/crm/projects/:name/wiki/save DONE
32 Сохранение/удаление скиллов PUT/DELETE /api/crm/projects/:name/skills/* DONE
33 Настройки аккаунта GET/PUT /api/crm/account/settings DONE
33 Создание проекта POST /api/crm/projects/create DONE
34 Issue CRUD POST/GET/PUT /api/mcp/issues/:project DONE
34 Wiki MCP PUT /api/mcp/wiki/:project DONE
34 Синхронизация roadmap GET/PUT /api/mcp/roadmap/:project DONE
34 CLI init GET /api/cli/init/:project/:mode DONE
35 Terminal log ingest POST /api/crm/projects/:name/terminal/log DONE
36 Cloud PM chat POST /api/crm/projects/:name/chat DONE
36.6 Генерация скиллов POST /api/crm/projects/:name/skills/generate DONE
36.7 Список ноутбуков GET /api/crm/projects/:name/notebooks DONE

| 40.14 | Roadmap (контент) | GET /api/mcp/roadmap/:project (+ поле content) | DONE |

Итого: 46+ эндпоинтов (REST + SSE + WebSocket + CLI/MCP)

NotebookLM Bridge (Phase 36.3)

Отдельный Python FastAPI-сервис на :19213 (только localhost). Не является частью Bun CRM API.

Эндпоинт Метод Назначение
/query POST Семантический поиск через Google NotebookLM
/sync POST Поставить источник в очередь асинхронной синхронизации (fire-and-forget)
/notebooks/init POST Создать ноутбук для нового проекта
/health GET Статус auth, количество ноутбуков, статистика очереди

Bun CRM инициирует fire-and-forget вызовы к bridge при:

Технический долг

Полный реестр в docs/backlog/technical-debt.md. Ключевые позиции:

ID Заголовок Статус
DEBT-2 Атомарные JSON-записи RESOLVED (Phase 22.3)
DEBT-3 Baked Docker token → session auth RESOLVED (Phase 31)
DEBT-6 CRM слепое пятно Master Bot RESOLVED (Phase 22.3)
DEBT-7 Защита от path traversal RESOLVED (Phase 22.3)

Поддерживается Rick (Orchestrator). Обновляется после завершения каждой фазы.