Architektura backendu CRM

Phase 22.0–48. Status: DEPLOYED (VPS aktywny, 68+ endpointów, architektura modułowa). Ostatnia aktualizacja: 2026-04-28 (Phase 48 — Dekompozycja Architektury)

Ogólny opis

Przeglądarka (:18888)
  │
  ├── Pliki statyczne ──► Nginx ──► Docker frontend (React + Nginx)
  │
  ├── /api/crm/* ────► Nginx ──► Bun Master Bot (:19210)
  │                                 ├── crmAuthMiddleware (HMAC-SHA256)
  │                                 └── shared/routes/router.ts → 17 modułów domenowych
  │
  ├── /api/auth/* ───► Nginx ──► Bun (:19210) → master-bot/routes/auth.ts
  ├── /api/cli/*  ───► Nginx ──► Bun (:19210) → master-bot/routes/cli.ts
  ├── /api/internal/* ► Bun (:19210, tylko loopback) → master-bot/routes/internal.ts
  │
  ├── /api/sse/* ────► Nginx (bez buforowania) ──► Bun (:19210)
  │
  └── /ws/terminal/* ► Nginx (upgrade) ──► Bun (:19210) → master-bot/routes/websocket.ts

Struktura modułów (Phase 48)

Serwer API (master-bot/api-server.ts, 196 linii) — cienki dispatcher:

Trasy CRM (shared/routes/router.ts, 512 linii) — tabela dispatchingu do 17 modułów domenowych:

Przepływ uwierzytelniania

Użytkownik ──► LoginOverlay (frontend)
         │
         ├── Email/hasło → POST /api/auth/login
         └── OAuth → /api/auth/{google,github} → callback
         │
         ▼
    Pomyślne uwierzytelnienie → token HMAC-SHA256
         │
         ├── payload = { sub: userId, iat, exp: +24h }
         ├── signature = HMAC-SHA256(base64url(payload), CRM_SECRET)
         └── token = base64url(payload) + "." + signature
         │
         ▼
    localStorage('crm-token') → Wszystkie wywołania API: Authorization: Bearer <token>
         │
         ▼
    Token wygasł → 401 → wymagane zalogowanie

Kluczowe pliki

Plik Rola
shared/auth.ts Generowanie/weryfikacja tokenów, email/OAuth, reset hasła, weryfikacja email, CORS, middleware
shared/vault.ts Przechowywanie sekretów AES-256-GCM (CRM_SECRET tworzony automatycznie)
frontend/src/crm/components/LoginOverlay.jsx UI logowania (email, OAuth, reset hasła, weryfikacja email)

Właściwości bezpieczeństwa

Trasy CRM API

Wszystkie trasy pod /api/crm/ wymagają Authorization: Bearer <token>.

GET /api/crm/projects

Lista wszystkich zarejestrowanych projektów ze statusem zdrowia na żywo. Zawiera mastera jako pseudo-projekt (DEBT-6).

Pole Źródło
name, type, bot_username config/bot_registry.json (master: wirtualny wpis, type: "system")
healthy Kontrola zdrowia HTTP (dzieci: port child, master: /api/master/health)
tmux_alive tmux has-session -t <nazwa> (master: citadel-master)
status Plik heartbeat lub pochodny ze zdrowia+tmux (master: healthy/degraded/down)

Master jest zawsze dołączany jako pierwszy element tablicy odpowiedzi.

GET /api/crm/projects/:name

Pełna karta szczegółów projektu.

Pole Źródło
manifest Tekst surowy z <cwd>/MANIFEST.md
heartbeat JSON z state/heartbeat_<nazwa>.json
skills[] Nazwy plików <cwd>/skills/*.md (z wyłączeniem _*.md)
healthy, tmux_alive Tak samo jak endpoint listy

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

Ogon logów strukturalnych JSONL.

Parametr Domyślnie Maks
lines 100 500
category dialog system, dialog, error

Źródło: /var/log/citadel/<nazwa>/<kategoria>-YYYY-MM-DD.log

Odczytuje najnowsze pliki logów jako pierwsze, parsuje JSONL, zwraca w kolejności chronologicznej.

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

Listing katalogu z ochroną przed path traversal.

Parametr Domyślnie Opis
path / Względna ścieżka wewnątrz cwd projektu

Bezpieczeństwo: resolve(base, requested) musi mieć startsWith(resolve(base)). Naruszenie → 403.

Elementy odpowiedzi: { name, type: "file"|"directory", size?, modified }, posortowane — katalogi pierwsze.

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

Zainstalowane skille z atrybucją źródła.

Źródło Wykrywanie
file <cwd>/skills/*.md (z wyłączeniem _*.md)
manifest MANIFEST.mdskills[] + library_skills[] (parsowanie JSON)

Deduplikowane: źródło plikowe ma priorytet nad manifestem.

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

Restartuj child bota przez watchdoga.

Aspekt Szczegół
Metoda Tylko POST (GET zwraca 405)
Auth Token HMAC-SHA256 (tak samo jak inne trasy CRM)
Cooldown 30 sekund per projekt (zwraca 429 jeśli zbyt często)
Mechanizm Wywołuje restartChild() z watchdog.ts
Odpowiedź { ok: true } lub { error: "..." }

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

Szereg czasowy metryk jakości dla wykresów sparkline.

Parametr Domyślnie Opis
days 7 Liczba dni do agregacji

Źródło: /var/log/citadel/<nazwa>/quality-events.log → liczby sukces/porażka per dzień.

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

Terminal WebSocket streamujący zawartość panelu tmux.

Aspekt Szczegół
Auth Parametr query ?token= (weryfikowany przez verifyToken().valid)
Tryb Domyślnie tylko do odczytu; ?mode=interactive umożliwia send-keys
Polling tmux capture-pane -p -e -t {sesja} -S -80 co 200ms
Delta Wysyła tylko gdy zawartość zmieniła się względem poprzedniej

Bezpieczeństwo: ochrona przed Path Traversal (Phase 22.3, DEBT-7)

Wszystkie endpointy walidują nazwy projektów przed przetworzeniem:

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

Stosowane w 3 punktach wejścia: routeCrmRequest(), routeSseRequest(), handler upgrade WebSocket w bot.ts. Nieprawidłowe nazwy → 403 Forbidden.

Integralność danych: Atomiczne zapisy (Phase 22.3, DEBT-2)

Zapisy do rejestru w onboarding.ts stosują wzorzec atomiczny:

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

Kluczowe pliki

Plik Rola Linie
shared/crm-routes.ts 55+ handlerów + router routeCrmRequest() + isValidProjectName() ~4800
shared/sage.ts Sage Worker: runSageAnalysis(), runBenchmark(), sędzia LLM, testowanie A/B ~550
shared/db.ts SQLite jako SSOT: userQueries, projectQueries, skillQueries, benchmarkQueries, chatQueries ~600
shared/auth.ts Generowanie tokenów, weryfikacja, CORS, middleware ~100
master-bot/bot.ts Bun.serve: router HTTP + handler terminala WebSocket ~1160
master-bot/onboarding.ts atomicWriteJson() do zapisów w rejestrze ~850

Proxy Nginx

Plik: infra/nginx/citadel-crm.conf — zastępuje starszy config citadel-os.

Trzy upstream:

Upstream Port Usługa
crm_backend 19210 Bun Master Bot (CRM API, zdrowie)
phaser_frontend 18889 Docker Phaser office (legacy)
mcp_bridge 19200 Docker MCP state-bridge (legacy)
Lokalizacja Backend Auth Specjalna konfiguracja
/ phaser_frontend basic auth Docker Phaser frontend
/api/crm/ crm_backend token (bez basic) timeout 30s, keepalive
/api/master/health crm_backend brak Publiczna kontrola zdrowia
/api/sse/ crm_backend token (bez basic) proxy_buffering off, timeout 1h (Phase 22.1)
/ws/ crm_backend token (bez basic) Upgrade WebSocket, timeout 1h (Phase 22.1)
/api/ mcp_bridge brak (bez basic) Starsze state API + SSE
/health mcp_bridge brak Starsze zdrowie bridge

Wszystkie lokalizacje przekazują X-Real-IP, X-Forwarded-For, X-Forwarded-Proto.

Zablokowane ścieżki: /.* (dotfiles), /config/, /state/, /scripts/.

Uwaga: Priorytet lokalizacji nginx zapewnia, że /api/crm/ i /api/sse/ są dopasowane przed catch-all /api/ (legacy MCP).

Integracja w Master Bocie

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

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

Router dispatchuje do funkcji handlerów z shared/crm-routes.ts. Nagłówki CORS są wstrzykiwane do każdej odpowiedzi CRM (włącznie z błędami 401).

Podsumowanie endpointów (wszystkie Phase)

Phase Funkcja Endpoint Status
22.0 Lista projektów GET /api/crm/projects DONE
22.0 Szczegóły projektu GET /api/crm/projects/:name DONE
22.0 Ogon logów GET /api/crm/projects/:name/logs DONE
22.0 Listing plików GET /api/crm/projects/:name/files DONE
22.0 Listing skilli GET /api/crm/projects/:name/skills DONE
22.1 Streaming logów SSE GET /api/sse/logs/:name DONE
22.1 Historia metryk GET /api/crm/projects/:name/metrics DONE
22.3 Restart projektu POST /api/crm/projects/:name/restart DONE
22.3 Terminal WebSocket WS /ws/terminal/:name DONE
24.5 CRUD specyfikacji GET/POST /api/crm/projects/:name/specs DONE
24.5 Aktywna rola GET/POST /api/crm/projects/:name/active-role DONE
25 Pakiet skilli GET /api/crm/projects/:name/skills-bundle DONE
25 Synchronizacja learnings GET/POST /api/crm/projects/:name/learnings DONE
26 CRUD workerów GET/POST /api/crm/projects/:name/workers DONE
32 Zapis wiki PUT /api/crm/projects/:name/wiki/save DONE
32 Zapis/usunięcie skilli PUT/DELETE /api/crm/projects/:name/skills/* DONE
33 Ustawienia konta GET/PUT /api/crm/account/settings DONE
33 Tworzenie projektu POST /api/crm/projects/create DONE
34 CRUD zgłoszeń POST/GET/PUT /api/mcp/issues/:project DONE
34 Wiki MCP PUT /api/mcp/wiki/:project DONE
34 Synchronizacja roadmapy GET/PUT /api/mcp/roadmap/:project DONE
34 Inicjalizacja CLI GET /api/cli/init/:project/:mode DONE
35 Ingest logów terminala POST /api/crm/projects/:name/terminal/log DONE
36 Chat Cloud PM POST /api/crm/projects/:name/chat DONE
36.6 Generowanie skilli POST /api/crm/projects/:name/skills/generate DONE
36.7 Listing notatników GET /api/crm/projects/:name/notebooks DONE

| 40.14 | Roadmapa (treść) | GET /api/mcp/roadmap/:project (+ pole content) | DONE |

Łącznie: 46+ endpointów (REST + SSE + WebSocket + CLI/MCP)

NotebookLM Bridge (Phase 36.3)

Osobna usługa Python FastAPI na :19213 (tylko localhost). Nie jest częścią Bun CRM API.

Endpoint Metoda Cel
/query POST Wyszukiwanie semantyczne przez Google NotebookLM
/sync POST Kolejkuj źródło do asynchronicznej synchronizacji (fire-and-forget)
/notebooks/init POST Utwórz notatnik dla nowego projektu
/health GET Status auth, liczba notatników, statystyki kolejki

Bun CRM wywołuje fire-and-forget do bridge przy:

Dług techniczny

Pełny rejestr: docs/backlog/technical-debt.md. Kluczowe pozycje:

ID Tytuł Status
DEBT-2 Atomiczne zapisy JSON ROZWIĄZANY (Phase 22.3)
DEBT-3 Zbakowany token Docker → auth sesji ROZWIĄZANY (Phase 31)
DEBT-6 Martwa strefa CRM master bota ROZWIĄZANA (Phase 22.3)
DEBT-7 Ochrona przed path traversal ROZWIĄZANA (Phase 22.3)

Prowadzone przez Ricka (Orkiestratora). Aktualizowane po ukończeniu każdej phase.