Архітектура CRM Backend
Фаза 22.0–48. Статус: DEPLOYED (живе на VPS, 68+ ендпоінтів, модульна архітектура). Останнє оновлення: 2026-04-28 (Фаза 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 domain modules
│
├── /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 only) → master-bot/routes/internal.ts
│
├── /api/sse/* ────► Nginx (unbuffered) ──► Bun (:19210)
│
└── /ws/terminal/* ► Nginx (upgrade) ──► Bun (:19210) → master-bot/routes/websocket.ts
Структура модулів (Фаза 48)
API Server (master-bot/api-server.ts, 196 рядків) — тонкий диспетчер:
master-bot/routes/auth.ts(562) — OAuth, login, register, 2FA, device codemaster-bot/routes/internal.ts(282) — chat/save, bridge-event, relay, завантаження бінарниківmaster-bot/routes/cli.ts(293) — інтеграція з ARC CLI + MCPmaster-bot/routes/websocket.ts(303) — terminal, event-stream, local-bridge
CRM Routes (shared/routes/router.ts, 512 рядків) — таблиця диспетчеризації для 17 доменних модулів:
projects.ts(723),workers.ts(636),skills.ts(665),chat.ts(862)sage.ts(757),onboarding.ts(601),files.ts(381),wiki.ts(258)system.ts,pins.ts,learnings.ts,docs.ts,specs.ts,restart.ts,analytics.ts_utils.ts(343) — спільні типи, безпека шляхів, auth-хелпери
Потік аутентифікації
User ──► LoginOverlay (frontend)
│
├── Email/password → POST /api/auth/login
└── OAuth → /api/auth/{google,github} → callback
│
▼
Successful auth → HMAC-SHA256 token
│
├── payload = { sub: userId, iat, exp: +24h }
├── signature = HMAC-SHA256(base64url(payload), CRM_SECRET)
└── token = base64url(payload) + "." + signature
│
▼
localStorage('crm-token') → All API calls: Authorization: Bearer <token>
│
▼
Token expires → 401 → login required
Ключові файли
| Файл | Роль |
|---|---|
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_SECRET: 32-байтний випадковий hex, зашифрований у vault (AES-256-GCM)
- Token TTL: 24 години, без поновлення
- Підпис: HMAC-SHA256, base64url encoding (без padding)
- CORS: Origin-echo, обмежено заголовками Authorization + Content-Type
- Верифікація email обов'язкова перед входом для нових акаунтів
- Базова авторизація nginx залишається як зовнішній шар defense-in-depth
Маршрути CRM API
Усі маршрути під /api/crm/ вимагають Authorization: Bearer <token>.
GET /api/crm/projects
Список усіх зареєстрованих проєктів з актуальним статусом здоров'я. Включає master як псевдо-проєкт (DEBT-6).
| Поле | Джерело |
|---|---|
name, type, bot_username |
config/bot_registry.json (master: віртуальний запис, type: "system") |
healthy |
HTTP health check (child-боти: child-порт, 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 |
Те саме, що в ендпоінті списку |
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.md → skills[] + library_skills[] (JSON parse) |
Дедуплікація: джерело file має пріоритет над manifest.
POST /api/crm/projects/:name/restart (Фаза 22.3)
Рестарт child-бота через watchdog.
| Аспект | Деталі |
|---|---|
| Метод | Тільки POST (GET повертає 405) |
| Auth | Токен HMAC-SHA256 (як і для інших CRM-маршрутів) |
| Cooldown | 30 секунд на проєкт (повертає 429, якщо занадто часто) |
| Механізм | Викликає restartChild() з watchdog.ts |
| Відповідь | { ok: true } або { error: "..." } |
GET /api/crm/projects/:name/metrics (Фаза 22.1)
Часовий ряд метрик якості для sparkline-графіків.
| Параметр | За замовчуванням | Опис |
|---|---|---|
days |
7 | Кількість днів для агрегування |
Джерело: /var/log/citadel/<name>/quality-events.log → лічильники success/failure по днях.
WS /ws/terminal/:name (Фаза 22.3)
WebSocket-термінал, що стрімить вміст tmux-панелі.
| Аспект | Деталі |
|---|---|
| Auth | Query-параметр ?token= (перевіряється через verifyToken().valid) |
| Режим | За замовчуванням read-only; ?mode=interactive вмикає send-keys |
| Polling | tmux capture-pane -p -e -t {session} -S -80 кожні 200мс |
| Delta | Надсилається тільки при зміні вмісту порівняно з попереднім |
Безпека: захист від Path Traversal (Фаза 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.
Цілісність даних: атомарні записи (Фаза 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 — заміняє застарілий конфіг citadel-os.
Три upstreams:
| 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 | Backend | Auth | Спеціальна конфігурація |
|---|---|---|---|
/ |
phaser_frontend |
basic auth | Docker Phaser frontend |
/api/crm/ |
crm_backend |
token (без basic) | 30s timeout, keepalive |
/api/master/health |
crm_backend |
none | Публічний health check |
/api/sse/ |
crm_backend |
token (без basic) | proxy_buffering off, 1h timeout (Фаза 22.1) |
/ws/ |
crm_backend |
token (без basic) | WebSocket upgrade, 1h timeout (Фаза 22.1) |
/api/ |
mcp_bridge |
none (без basic) | Legacy state API + SSE |
/health |
mcp_bridge |
none | Legacy bridge health |
Усі locations прокидають 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 → public, no 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 | Tail логів | 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-стрім логів | 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 | 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 (Фаза 36.3)
Окремий Python FastAPI-сервіс на :19213 (тільки localhost). Не є частиною Bun CRM API.
| Ендпоінт | Метод | Призначення |
|---|---|---|
/query |
POST | Семантичний пошук через Google NotebookLM |
/sync |
POST | Поставити джерело в чергу для async-синхронізації (fire-and-forget) |
/notebooks/init |
POST | Створити ноутбук для нового проєкту |
/health |
GET | Статус auth, кількість ноутбуків, статистика черги |
Bun CRM тригерить fire-and-forget виклики до Bridge у таких місцях:
handleUpdateIssue(закриття/відкриття задачі) →/synchandleMcpWikiUpdate(збереження вікі) →/synchandleCreateProject/handleOnboardingSetup→/notebooks/initexecuteAskNotebooklm→/query(timeout 15с, fallback на локальний пошук)
Технічний борг
Повний реєстр дивись у docs/backlog/technical-debt.md. Ключові пункти:
| ID | Назва | Статус |
|---|---|---|
| DEBT-2 | Атомарні JSON-записи | RESOLVED (Фаза 22.3) |
| DEBT-3 | Запечений Docker-токен → session auth | RESOLVED (Фаза 31) |
| DEBT-6 | Сліпа зона CRM щодо master-бота | RESOLVED (Фаза 22.3) |
| DEBT-7 | Захист від path traversal | RESOLVED (Фаза 22.3) |
Підтримується Rick (Orchestrator). Оновлюється після завершення кожної фази.