| 2026-10-04 |

feat: персональные MCP-ключи — выпуск в SPA, роль владельца у тула
...
Решение владельца (2026-10-04):
- Каждый пользователь выпускает себе MCP-ключ на новой странице /mcp-keys
(доступна любой роли); plaintext показывается один раз, в БД — sha256 + hint.
- Ключ действует ролью владельца на момент выпуска (снейпшот system_role):
admin/superadmin — полный каталог тулов (35), user — только личный набор
(me, my_events_list, my_push_subscriptions_list, my_push_subscription_delete).
- Статический MCP_TOKEN из .env остаётся суперадмин-бэкдором; /mcp монтируется
безусловно (персональные ключи работают и без env-токена).
Backend:
- таблица mcp_tokens (миграция 0008 c9a2d6e71f43) — зеркало ApiKey;
- app/mcp/context.py — ContextVar пользователя, resolve_personal_token;
- McpTokenGuard резолвит стат.токен или mcp_* в БД, пишет last_used_at,
401 на промахе (чистый ASGI, SSE не трогает);
- тулы: user=current_mcp_user() вместо синтетического MCP_USER; админ-тулы
под _admin_run — для user-ключа ToolError с {"error": 403} (не return-данные:
FastMCP валидирует вывод по аннотации, list-тулы бы отвергли dict);
- HTTP: GET/POST /api/v1/me/mcp_tokens (+/revoke), GET/POST
/api/v1/admin/mcp_tokens[.../revoke], лимит 10 активных на пользователя.
SPA: страница McpKeysView (свои ключи + как подключить агент; админ — все
ключи и отзыв), маршрут /mcp-keys (userOk), пункт в навигации обеих ролей.
Доки: docs/07 переписан (три вида доступа, 35 тулов), CLAUDE.md,
docs/08 (каталог 35), .env.example (MCP_TOKEN опционален).
E2e на живом стеке: user-ключ — 403 на админ-тулах и рабочий личный набор;
admin-ключ — полный каталог; last_used_at; бэкдор = superadmin; отзыв -> 401.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
12 hours ago
|
| 2026-10-03 |

feat: MCP-сервер встроенным FastMCP (/mcp) + архив вместо удаления
...
MCP (docs/07-mcp.md):
- embedded FastMCP streamable-http на /mcp того же контейнера api;
гейт — чистый ASGI McpTokenGuard по Bearer MCP_TOKEN из .env (пусто —
/mcp выключен). Route, не Mount: Mount('/mcp') не матчит точный /mcp.
- 31 тул в app/mcp/tools.py: выдача ключей клиенту (key_issue — plaintext
один раз), полный CRUD источников/типов/целей/правил, поток (events/
deliveries), push-подписки, настройки, send_test_event.
- тула переиспользуют функции admin-routes напрямую (синхронные def без
срабатывания Depends); HTTPException/ValidationError — как данные.
Архив вместо удаления (миграция 0007 b7d3e8f04a92):
- deleted_at на sources/notification_types/channel_targets/routing_rules +
частичные live-индексы; DELETE = 204 → архив, POST .../restore → 200.
- созд поверх архива → 409 с подсказкой id; ключ архивного источника →
401; тройка архивного типа → 422; целевые правки → 422; архивная цель в
правиле — Delivery skipped с аудитом; история имена архива показывает.
- push-подписки — исключение: жёсткое удаление.
- fix list_settings: оверрайды DB-only ключей (push/smtp, без .env-дефолта)
снова возвращаются в value.
Проверено: mcparch ALL OK (41), mcpcheck ALL OK (28, MCP SDK-клиент),
mecheck/retcheck ALL OK, UI-регрессия (SPA, источники+delete 204) ALL OK.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|

feat: раздел Настройки (.env-оверрайды UI) и PWA с web-push каналом
...
Настройки: реестр app/settings_registry.py + хранение оверрайдов в
app_settings (settings_store, без кэша). Дефолт из .env, перебивается из
админки без перезапуска. Секреты (VAPID private, тг-токен, SMTP-пароль)
— write-only: записываются через UI, никогда не возвращаются API.
Эндпоинты GET/PUT /api/v1/admin/settings.
Web-push: канал push (целенаправлен как user — адресат из payload.user_id),
таблица push_subscriptions, самопользовательская подписка браузера через
/me/push* (upsert по endpoint, чужой 404). Воркер send_push: pywebpush
(DER base64url ключи), 404/410 — подписка удаляется, ретраи как у s2s.
PWA: manifest + sw.js (pass-through, push/notificationclick), иконки из
gnexus-mark.svg, /settings с личным пуш-блоком и админ-конфигами.
Миграция 0006 (ck_channel ×3 +push, app_settings, push_subscriptions).
Верификация: 47 бэкенд-проверок + mecheck/retcheck + 17 puppeteer — ALL OK.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|

feat: многопользовательский режим — личный лог по каналу user
...
- Конвенция payload.user_id (sub gnexus-auth): контракт v1 цел, получателей
в конверте нет — дописка в docs/05-ingestion-api.md.
- Канал user в CHANNELS: правило с действием user направляет событие в личный
лог пользователя; без payload.user_id — skipped-delivery с причиной (аудит).
- deliveries.recipient_user_id + ix_deliveries_recipient; миграция 0004
пересоздаёт ck_channel на всех трёх таблицах (deliveries, routing_rule_actions,
channel_targets).
- Routing: _throttled по recipient для user-канала; s2s без цели — прежний
ретрай-цикл.
- Правила: действие user валидно без цели, user + target_id — 422; TargetIn
по-прежнему отвергает user.
- GET /api/v1/me (любой аутентифицированный) и GET /api/v1/me/events (личный
лог строго по своему sub, дедуп по событию).
- OAuth-колбэк: токен выдаётся любому аутентифицированному, роль решает только
раздел; adмин-API остаётся под 403.
- SPA: роль-гейт в router (user -> /my-events, прямой заход в админ-маршруты
редиректит), нав от роли, MyEventsView («Мои события»), getSession-кэш.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Правки второй волны: редактирование цели, вёрстка, тултипы, ссылки
...
- Цель: PATCH /admin/targets/{id} (частичное обновление, канал не
меняется; 409 на дубль имени в канале) + редактирование на странице
цели: название/описание/config-JSON с валидацией, switch вкл/выкл.
- Тултипы заголовков таблиц: панель кита раскрывается ВНИЗ поверх
таблицы — иначе обрезается overflow у .table-wrapper.
- Правила: колонка «Условия» из таблицы убрана (только на странице
правила); вёрстка действий на странице правила починена — голова
(номер/канал/цель) и тело (шаблон) в отдельные строки.
- Доставки: кнопка обновления выровнена по вертикали с контролами
фильтров.
- Источники/цели: у строк убрана кнопка просмотра; slug/название —
кликабельная ссылка на страницу, удаление — единственная кнопка.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Admin API (#32): CRUD конфигурации и поток под гейтом ролей
...
/api/v1/admin (require_admin): sources (+keys create/revoke), types,
targets, rules (POST/PATCH actions с заменой, toggle enabled), events и
deliveries списки с фильтрами. Конфликты — 409 с человекочитаемой
причиной; защита источника с событиями от удаления. Создание ключа
возвращает токен ровно один раз.
Проверено: TestClient с подменой require_admin против живой БД —
полный цикл create/dup-409/bad-target-422/patch/list/delete/revoke.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|