main
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# ADR-0001: Vendored gbrain через git mirror
|
||||
|
||||
**Дата:** 2026-05-20
|
||||
**Статус:** Принято
|
||||
|
||||
## Контекст
|
||||
|
||||
gbrain (https://github.com/garrytan/gbrain) — активно развивающийся проект:
|
||||
- 60+ коммитов за последние 2 месяца
|
||||
- v0.26 (текущая) ввела breaking change: переход с bearer tokens на OAuth 2.1
|
||||
- Public API субпакетов (`gbrain/operations`, `gbrain/pglite-engine`) меняется
|
||||
|
||||
Дополнительные факторы:
|
||||
- GitHub аккаунт zuevav был ограничен в апреле 2026, доступ к публичным репозиториям через `bun install -g github:garrytan/gbrain` может неожиданно ломаться
|
||||
- ZBrain зависит от стабильности gbrain в production
|
||||
- При следующей блокировке аккаунта мы не сможем переустановить или обновить gbrain
|
||||
|
||||
## Решение
|
||||
|
||||
Не зависеть от GitHub в runtime/install path. Зеркалить gbrain в свой git:
|
||||
|
||||
```bash
|
||||
# Однократно, на чистом ноуте с работающим GitHub доступом:
|
||||
git clone --mirror https://github.com/garrytan/gbrain.git
|
||||
cd gbrain.git
|
||||
git remote set-url --push origin git@git.zetit.ru:zuevav/gbrain-mirror.git
|
||||
git push --mirror
|
||||
```
|
||||
|
||||
Bootstrap-скрипт клонит из git.zetit.ru:
|
||||
|
||||
```bash
|
||||
GBRAIN_REPO="https://git.zetit.ru/zuevav/gbrain-mirror.git" \
|
||||
GBRAIN_VERSION="v0.26.5" \
|
||||
bash scripts/bootstrap-vm.sh
|
||||
```
|
||||
|
||||
## Последствия
|
||||
|
||||
### Плюсы
|
||||
- Независимость от GitHub доступности и блокировок аккаунтов
|
||||
- Управляемое обновление gbrain (через `git fetch upstream && review && push to mirror`)
|
||||
- Возможность hotfix'ов локально, если upstream сломан (как форк, но в общем git)
|
||||
- Защита от supply chain атак (мы видим diff перед обновлением)
|
||||
|
||||
### Минусы
|
||||
- Нужно вручную обновлять mirror (раз в месяц-два)
|
||||
- Лишний шаг в setup
|
||||
- Расхождение с upstream если хотфикс не запушим обратно
|
||||
|
||||
### Митигация
|
||||
- Раз в месяц: проверка upstream'а, review diff, push to mirror
|
||||
- Все наши изменения (если потребуются) — в отдельной ветке `zetit/*`, чтобы не мерджились с upstream
|
||||
- Tag'ируем версии в нашем mirror'е (`v0.26.5-zetit-1`, если есть наши патчи)
|
||||
|
||||
## Альтернативы рассмотренные
|
||||
|
||||
1. **Прямая зависимость от github:garrytan/gbrain** — отвергнуто из-за рисков блокировки и breaking changes
|
||||
2. **Свой fork на git.zetit.ru с активной разработкой** — overkill, мы не планируем существенно дорабатывать gbrain
|
||||
3. **npm-publish gbrain в свой private npm registry** — gbrain не публикуется в npm как пакет; нужен install via bun + workspace
|
||||
@@ -0,0 +1,92 @@
|
||||
# ADR-0002: Two-zone deployment (внутренний + публичный контур)
|
||||
|
||||
**Дата:** 2026-05-20
|
||||
**Статус:** Принято
|
||||
|
||||
## Контекст
|
||||
|
||||
ZBrain должен обслуживать:
|
||||
|
||||
1. **Внутренних пользователей** — owner (Алексей) и будущая команда работают через VPN/локальную сеть ZETIT. Им нужен полный UI: дашборд, управление брейнами, токенами, источниками, аудит.
|
||||
|
||||
2. **Внешних агентов** — Claude Code на серверах клиентов (TeleraPharma, Fontvielle, и т.д.), которым нужен MCP-доступ к чтению/записи брейнов. У этих серверов нет VPN-доступа к ZETIT.
|
||||
|
||||
Простое решение "выставить всё на публичный домен с auth" имеет недостатки:
|
||||
- Любая уязвимость в админ-UI становится публично доступной
|
||||
- OAuth flow на админ-эндпоинтах усложняет admin интеграции (curl, скрипты)
|
||||
- Утечка session cookie с админ-привилегиями через CSRF/XSS критична
|
||||
|
||||
## Решение
|
||||
|
||||
Развести функционал по двум сетевым контурам с разными доменами:
|
||||
|
||||
### Внутренний контур (brain.zetit.local)
|
||||
|
||||
- Доступен **только** из сети ZETIT и через VPN
|
||||
- Plain HTTP допустим (доверенная сеть, нет MITM риска)
|
||||
- Endpoints: **всё** — `/`, `/api/*`, `/mcp/*`, `/api/events` (SSE)
|
||||
- Auth: session cookie через local login (email/password) или OAuth
|
||||
- Используется: для web-UI, админских скриптов, debug'а
|
||||
|
||||
### Публичный контур (brain.zetit.ru)
|
||||
|
||||
- Доступен из интернета через TLS
|
||||
- Endpoints: **только** `/mcp/*`, `/oauth/*`, `/health`
|
||||
- Auth: Bearer tokens (для MCP) или OAuth flow (для подключения нового клиента)
|
||||
- Любой другой path → 404 (без подсказок что вообще существует)
|
||||
- Rate limiting per IP и per token
|
||||
- Используется: для удалённого Claude Code/Cursor, для машинного доступа
|
||||
|
||||
## Реализация
|
||||
|
||||
Один Node.js процесс запускает два независимых Express app на разных портах:
|
||||
|
||||
```typescript
|
||||
// apps/api/src/main.ts
|
||||
const internalApp = createInternalApp(); // полный набор routes
|
||||
const publicApp = createPublicApp(); // только /mcp и /oauth
|
||||
|
||||
internalApp.listen(3000, '127.0.0.1');
|
||||
publicApp.listen(3010, '127.0.0.1');
|
||||
```
|
||||
|
||||
Nginx разруливает по hostname:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
server_name brain.zetit.local;
|
||||
location / { proxy_pass http://127.0.0.1:3000; }
|
||||
}
|
||||
|
||||
server {
|
||||
server_name brain.zetit.ru;
|
||||
listen 443 ssl;
|
||||
location /mcp/ { proxy_pass http://127.0.0.1:3010; }
|
||||
location /oauth/ { proxy_pass http://127.0.0.1:3010; }
|
||||
location / { return 404; }
|
||||
}
|
||||
```
|
||||
|
||||
## Последствия
|
||||
|
||||
### Плюсы
|
||||
- Атака на админ-UI требует VPN или физического доступа к сети
|
||||
- Невозможно случайно выставить админ-API в публичный контур (architectural enforcement)
|
||||
- Rate limit на публичном контуре более агрессивный, не мешает админам
|
||||
- Раздельные access логи nginx для security audit
|
||||
|
||||
### Минусы
|
||||
- Сложнее тестировать: нужно эмулировать два контура локально
|
||||
- DNS: две A-записи на разных серверах (внутренний и edge)
|
||||
- Невозможно "быстро дать админский доступ" партнёру не давая VPN
|
||||
|
||||
### Митигация для разработки
|
||||
- Локально оба app слушают `127.0.0.1` на разных портах, host header проверяется в middleware
|
||||
- `.env.development` имеет флаг `DISABLE_ZONE_CHECK=true` для упрощённого dev режима
|
||||
- Integration тесты гоняют оба контура
|
||||
|
||||
## Альтернативы рассмотренные
|
||||
|
||||
1. **Один контур с строгой ролевой моделью** — отвергнуто, ошибка в RBAC становится критичной уязвимостью
|
||||
2. **Два разных процесса/контейнера** — overkill для одной VM, осложняет shared state (token cache)
|
||||
3. **API Gateway (Kong/Traefik)** — лишняя сущность для проекта такого размера
|
||||
@@ -0,0 +1,103 @@
|
||||
# ADR-0003: Изоляция брейнов через отдельные PostgreSQL БД
|
||||
|
||||
**Дата:** 2026-05-20
|
||||
**Статус:** Принято
|
||||
|
||||
## Контекст
|
||||
|
||||
ZBrain хостит несколько брейнов с разным уровнем чувствительности данных:
|
||||
|
||||
- **zetit** — внутренние процессы ZETIT, не публичные
|
||||
- **telerapharma** — корпоративная инфраструктура с NDA
|
||||
- **personal** — личные данные владельца
|
||||
- **community** — данные о Смоленской 10, дом-чат, ЖКХ
|
||||
|
||||
Утечка MCP токена с правами на один брейн **не должна** давать доступ к другим брейнам.
|
||||
|
||||
Варианты изоляции в Postgres (от слабой к сильной):
|
||||
|
||||
1. **Одна БД, одна схема, отдельные таблицы с префиксами** — слабая изоляция, любой SQL injection даёт доступ ко всему
|
||||
2. **Одна БД, отдельные schema'ы (zetit.pages, telerapharma.pages)** — изоляция через `search_path`, но один Postgres user видит всё
|
||||
3. **Одна БД, отдельные пользователи с row-level security** — сложно, легко допустить ошибку в RLS политиках
|
||||
4. **Отдельные БД с отдельными пользователями** — изоляция на уровне СУБД
|
||||
|
||||
## Решение
|
||||
|
||||
Вариант 4: **отдельная БД + отдельный Postgres-пользователь на каждый брейн**.
|
||||
|
||||
```
|
||||
PostgreSQL cluster
|
||||
├── brainhub (БД)
|
||||
│ └── владелец: brainhub
|
||||
│ └── таблицы: users, sessions, tokens, brains, sources, projects, audit_log
|
||||
│
|
||||
├── gbrain_zetit (БД)
|
||||
│ └── владелец: gbrain_zetit
|
||||
│ └── полная схема gbrain (pages, chunks, embeddings, links, ...)
|
||||
│
|
||||
├── gbrain_telerapharma (БД)
|
||||
│ └── владелец: gbrain_telerapharma
|
||||
│ └── ...
|
||||
│
|
||||
├── gbrain_personal (БД)
|
||||
│ └── владелец: gbrain_personal
|
||||
│ └── ...
|
||||
│
|
||||
└── gbrain_community (БД)
|
||||
└── владелец: gbrain_community
|
||||
└── ...
|
||||
```
|
||||
|
||||
Каждый gbrain-instance подключается к Postgres под своим пользователем и **физически не видит** другие БД:
|
||||
|
||||
```sql
|
||||
-- Под пользователем gbrain_zetit
|
||||
\l
|
||||
-- Покажет только gbrain_zetit (и template/postgres)
|
||||
|
||||
\c gbrain_telerapharma
|
||||
-- ERROR: permission denied for database gbrain_telerapharma
|
||||
```
|
||||
|
||||
Brainhub использует свою БД `brainhub` под пользователем `brainhub` (тоже не видит остальные).
|
||||
|
||||
## Реализация
|
||||
|
||||
В `scripts/create-brain.sh`:
|
||||
|
||||
```sql
|
||||
CREATE USER gbrain_${BRAIN_NAME} WITH PASSWORD '${random_password}';
|
||||
CREATE DATABASE gbrain_${BRAIN_NAME} OWNER gbrain_${BRAIN_NAME};
|
||||
GRANT ALL PRIVILEGES ON DATABASE gbrain_${BRAIN_NAME} TO gbrain_${BRAIN_NAME};
|
||||
-- НЕ выдаём этому юзеру ничего на other databases
|
||||
```
|
||||
|
||||
Пароль для каждого gbrain-юзера генерируется случайно и хранится:
|
||||
1. В `/var/lib/zbrain/brains/<name>/config.json` (chmod 600, owner zbrain)
|
||||
2. В DATABASE_URL внутри systemd unit (Environment="DATABASE_URL=...")
|
||||
|
||||
`brainhub` НЕ хранит эти пароли в своей БД (они не нужны для admin-операций) — только знает на каком порту слушает gbrain instance.
|
||||
|
||||
## Последствия
|
||||
|
||||
### Плюсы
|
||||
- Физическая изоляция через ACL Postgres
|
||||
- SQL injection в одном брейне не даёт доступ к другим
|
||||
- pg_dump каждого брейна тривиально по отдельности
|
||||
- Удаление брейна = одна команда `DROP DATABASE gbrain_X`
|
||||
- Разные параметры тюнинга на разные брейны (если потребуется)
|
||||
|
||||
### Минусы
|
||||
- Нельзя сделать JOIN между брейнами в SQL (но это и не нужно — поиск через brainhub)
|
||||
- Каждая БД имеет свои индексы, общая нагрузка на shared_buffers Postgres делится
|
||||
- Backup делает дамп каждой БД отдельно (несложно)
|
||||
|
||||
### Митигация
|
||||
- shared_buffers задан с запасом (2GB на 8GB RAM)
|
||||
- HNSW индексы строятся в `maintenance_work_mem=512MB`, одного брейна за раз достаточно
|
||||
|
||||
## Альтернативы рассмотренные
|
||||
|
||||
1. **Row-level security в одной БД** — сложно, требует консистентного применения политик на каждой таблице gbrain (а у gbrain их десятки). Один пропущенный CREATE POLICY = утечка
|
||||
2. **Отдельные Postgres-кластеры на каждый брейн** — overkill, 4× процессов, 4× shared_buffers
|
||||
3. **Отдельные VM на каждый брейн** — экстремальная изоляция, не нужна на нашем масштабе
|
||||
Reference in New Issue
Block a user