Files
ZBrain/docs/ARCHITECTURE.md
T
zuevav f4bca8449e main
2026-05-20 19:33:02 +03:00

13 KiB
Raw Blame History

Архитектура ZBrain

Обзор

ZBrain — трёхслойная система:

  1. gbrain (vendored) — движок: хранилище, эмбеддинги, гибридный поиск, MCP server
  2. brainhub (наш код) — админка, прокси, auth, RBAC, audit
  3. PostgreSQL — единая СУБД с изоляцией по БД на каждый брейн

Принципы

Изоляция данных. Каждый брейн (zetit, telerapharma, personal, community) живёт в отдельной БД с отдельным Postgres-пользователем. Утечка токена с правами на personal не даёт доступа к telerapharma на уровне СУБД.

Brainhub как единая точка входа. Никакой Claude Code никогда не подключается к gbrain напрямую. Все MCP-запросы проходят через brainhub-proxy для аудита, rate limiting, проверки scope.

Vendored gbrain. Зависимость от upstream'а закреплена в нашем git mirror'е с конкретным тегом. Обновления gbrain — управляемое решение, а не автоматический breaking change.

Two-zone deployment. Внутренний контур (VPN, plain HTTP) для полного UI и админ-API; публичный контур (HTTPS + OAuth) только для /mcp/* и /oauth/*.

Компонентная диаграмма

┌─────────────────────────────────────────────────────────────────┐
│                          Clients                                │
├─────────────────────────────────────────────────────────────────┤
│  Claude Code (dev)    Claude Code (prod)    Cursor    Browser   │
│       │                    │                  │         │       │
└───────┼────────────────────┼──────────────────┼─────────┼───────┘
        │                    │                  │         │
   internal               public via         internal  internal
   MCP                    nginx              MCP       HTTPS
        │                    │                  │         │
        ▼                    ▼                  ▼         ▼
┌─────────────────────────────────────────────────────────────────┐
│                       Brainhub Process                          │
│  ┌─────────────────────┐  ┌────────────────────────────────────┐│
│  │ Internal Server     │  │ Public Server                      ││
│  │ :3000               │  │ :3010                              ││
│  │  - /api/*           │  │  - /mcp/* (с auth)                 ││
│  │  - /mcp/* (с auth)  │  │  - /oauth/*                        ││
│  │  - /api/events SSE  │  │                                    ││
│  └──────────┬──────────┘  └──────────────┬─────────────────────┘│
│             │                            │                      │
│             └────────┬───────────────────┘                      │
│                      │                                          │
│   ┌──────────────────┴──────────────────┐                       │
│   │  Shared modules                     │                       │
│   │   - Auth service (passport.js)      │                       │
│   │   - MCP Proxy + Token validator     │                       │
│   │   - RBAC middleware                 │                       │
│   │   - Audit logger                    │                       │
│   │   - SSE event bus                   │                       │
│   │   - Sync worker queue               │                       │
│   └──────────────────┬──────────────────┘                       │
│                      │                                          │
└──────────────────────┼──────────────────────────────────────────┘
                       │
        ┌──────────────┴────────────┬─────────────┐
        │                           │             │
        ▼                           ▼             ▼
┌───────────────┐        ┌────────────────┐   ┌──────────────┐
│  Postgres     │        │  gbrain inst.  │   │  CLI (exec)  │
│  brainhub DB  │        │  HTTP MCP      │   │              │
│  - users      │        │  127.0.0.1:    │   │  gbrain      │
│  - sessions   │        │   3001..3099   │   │   init       │
│  - tokens     │        │                │   │   import     │
│  - brains     │        │  21 MCP tools  │   │   sync       │
│  - sources    │        │                │   │              │
│  - projects   │        └────────┬───────┘   └──────┬───────┘
│  - audit_log  │                 │                  │
└───────────────┘                 ▼                  ▼
                          ┌─────────────────────────────────┐
                          │  Postgres (per-brain DBs)       │
                          │  - gbrain_zetit                 │
                          │  - gbrain_telerapharma          │
                          │  - gbrain_personal              │
                          │  - gbrain_community             │
                          │                                 │
                          │  Каждая со своим owner-user'ом  │
                          └─────────────────────────────────┘

Flow: MCP-запрос от Claude Code

Допустим, Claude Code на сервере клиента делает search-запрос:

1. Claude Code:
   POST https://brain.zetit.ru/mcp/zetit
   Authorization: Bearer brain_pat_a8f3b2c1...
   Content-Type: application/json
   Body: {"jsonrpc":"2.0", "method":"tools/call", "params":{"name":"query","arguments":{"query":"how to renew sstp cert"}}}

2. Nginx (brain.zetit.ru):
   - rate_limit per IP (30 r/s) и per token (100 r/s)
   - проксирует на 127.0.0.1:3010

3. Brainhub public server:
   - MCPProxy.middleware валидирует токен
   - извлекает token из Bearer, считает SHA-256
   - смотрит в кэш (in-memory, TTL 30s)
     - если нет в кэше: SELECT FROM access_tokens WHERE token_hash = ?
     - проверки: revoked_at IS NULL, expires_at > now(), IP в allowlist
   - проверяет scope: содержит ли scopes[] 'mcp:read:zetit' или 'mcp:write:zetit'
   - резолвит брейн: SELECT mcp_internal_port FROM brains WHERE name = 'zetit'
   - пишет в audit_log queue: {action: 'mcp.call', resource: 'zetit', token_id: ..., method: 'query'}
   - throttled update: token.last_used_at = now() (не чаще раза в минуту)
   - проксирует на http://127.0.0.1:3001/mcp

4. gbrain (zetit instance):
   - выполняет hybrid search
   - возвращает результаты

5. Brainhub возвращает Claude Code stream of bytes как есть

Времена:

  • Без кэша: ~3-5ms (DB lookup) + gbrain латенси
  • С кэшем: ~0.5ms (cache hit) + gbrain латенси
  • Audit batch insert: раз в секунду, async, не влияет на латенси

Структура процесса brainhub

Один Node.js процесс с двумя Express apps на разных портах:

// apps/api/src/main.ts
const internalApp = createInternalApp(); // /api, /mcp (с auth), UI proxy
const publicApp = createPublicApp();      // /mcp, /oauth, /health

internalApp.listen(3000, '127.0.0.1');    // только localhost
publicApp.listen(3010, '127.0.0.1');      // только localhost

Nginx разруливает между ними по hostname:

  • brain.zetit.local → 127.0.0.1:3000
  • brain.zetit.ru → 127.0.0.1:3010

Это даёт shared codebase, но чёткое разделение endpoints. Невозможно случайно выставить admin API в публичный контур.

Sync worker

Sync — долгая операция (минуты до часов для большого корпуса). Не делать в-process на Express запросе.

// Используем BullMQ или нативный setInterval с in-memory очередью

// API endpoint только ставит задачу в очередь
POST /api/brains/zetit/sources/git-1/sync
   syncQueue.add('sync', {brainId, sourceId})
   return {jobId}

// Worker (отдельный процесс или setInterval):
syncQueue.process('sync', async (job) => {
  const {brainId, sourceId} = job.data;
  const source = await db.sources.findById(sourceId);

  // git pull
  await exec(`cd ${source.config.local_path} && git pull`);

  // запуск gbrain через CLI
  const process = spawn('gbrain', ['sync', '--source', source.config.local_path], {
    env: {...process.env, DATABASE_URL: brain.database_url}
  });

  // streaming output через SSE
  process.stdout.on('data', (chunk) => {
    sseEventBus.publish(`brain:${brainId}:sync:${sourceId}`, chunk.toString());
  });

  // update source.last_sync_at
});

Cron триггерится из того же worker по расписанию (source.schedule = '0 */6 * * *').

Кэширование

Что Где TTL Зачем
Token validation Memory (LRU) 30s избежать DB hit на каждом MCP запросе
Brain port resolution Memory 5min редко меняется
User session Memory 5min избежать DB hit на каждом auth check
OAuth state Redis (опционально) 10min CSRF protection для OAuth flow

В первой версии всё in-memory. Если процесс будет масштабироваться горизонтально — выносим в Redis.

Failure modes и их обработка

Что ломается Что происходит Что делаем
Postgres down brainhub не отвечает health check 503, alerts
gbrain instance crash конкретный брейн не отвечает brainhub возвращает 502 на /mcp/, остальные работают
OpenAI API down sync падает, search работает (без re-embed) retry с backoff в worker
FNA proxy down gbrain не может делать embed sync queue ждёт, alerts
Disk full Postgres перестаёт писать Prometheus alert заранее на 80%
Token leaked злоумышленник делает запросы Revoke в UI → cache invalidate в течение 30s

Что НЕ делаем в первой версии

Сознательно откладываем, чтобы не утонуть:

  • Horizontal scaling brainhub (один процесс на одной VM достаточно для нашего масштаба)
  • Sharding по брейнам на разные хосты (всё в одном Postgres)
  • Многотенантность (только наши пользователи, не SaaS)
  • WebSockets (SSE достаточно для realtime UI)
  • Service mesh, k8s (overkill для одной VM)
  • Свой forked gbrain (используем upstream через mirror)

Эти решения можно пересмотреть через 6-12 месяцев на реальных метриках.