| 2026-10-04 |

Синхронизация с gnexus-handbook: healthz, /mcp-protocol/, блокировка, i18n
...
Часть A — точечные несоответствия:
- GET /healthz (alias к /api/healthz) — требование хендбука 10-platform/health.
Часть C — MCP второго маршрута и блокировка:
- /mcp-protocol/ вторым маршрутом (конвенция 10-platform/mcp.md, слэш значим
для реверс-прокси); FastMCP-саб-аппу скармливается канонический /mcp.
/mcp остаётся историческим alias'ом (docs/07).
- Вебхуки gnexus-auth user.blocked/deleted/archived и user.unblocked/restored
ставят/снимают флаг в новой таблице user_prefs (миграция 0009); resolve_
personal_token гонит заблокированных и отвечает тем же 401, что и при
неверном токене. Статический MCP_TOKEN не затрагивается. SSO-логаут
токены не трогает (решение владельца).
Часть B — i18n по конвенции 10-platform/i18n.md (ru база + en + uk):
- frontend/src/i18n/ — модуль по паттерну gnexus-creds (t() c интерполяцией
и славскими плюрализациями, fallback en) + словари-сообщения (386 slug'ов);
все 15 вьюх и App.vue переведены на t(), module-консты с текстами — в
computed (грабли i18n.md); ui.js fmtTime — на Intl.DateTimeFormat;
push.js/local строки — в словарь.
- Язык: override в user_prefs.locale, эффективный = override → locale аккаунта
(profile.locale, нормализация до 2-буквенного префикса) → en; GET /me
возвращает locale/locale_effective, PATCH /me принимает en|uk|ru|auto.
Выбор языка — в Настройках (всем ролям), опции родными именами.
- Серверная локализация: Accept-Language от SPA; app/i18n_server.py —
каталог точных строк + regex-паттерны для параметризованных detail
(exception-handler StarletteHTTPException в main.py); labels/help реестра
настроек локализуются на лету.
- apiFetch: шлёт Accept-Language; 422 detail (список) собирается в строку.
Хендбук (отдельный репо): notifications.md — конвенция интеграции сервисов
(источник/ключ/конверт v1/client-libs/webhook-цель), mcp.md и auth.md —
референс-варианты Synapse, чеклист нового сервиса ссылается на notifications.md.
Eugene Sukhodolskiy
committed
5 hours ago
|
docs: конвенции экосистемы — ссылка на gnexus-handbook
...
Хендбук (~/Projects/gnexus-handbook) — каноническое место правил
(SSO, MCP-токены, секреты, health, уведомления); по его же правилу
не дублируем в CLAUDE.md — ссылаемся.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
6 hours ago
|

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
|
fix: ссылки на gnexus-mark в LoginView и docs/06 (дополнение к e00539e)
...
Правки были сделаны в рабочем дереве, но не попали в коммит e00539e
(он снял только удаление файла). Замена на synapse-mark.svg и текст
docs/06 про иконки PWA — в этом коммите.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
12 hours ago
|
fix: знак Synapse на экране входа; доки PWA про synapse-mark
...
- LoginView по-прежнему показывала gnexus-mark.svg — заменена на
synapse-mark.svg (как favicon и нав-бар с 808b4c3/b4a33e2)
- docs/06: иконки PWA теперь synapse-icon-*.png, не из gnexus-mark.svg
- gnexus-mark.svg удалён из public/ — ссылок больше нет, dist пересобран
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
13 hours ago
|
docs: docs/09 — DX v0.1.2 (user_id, wait_for_status) и пин v0.1.2
...
- user_id — первый класс параметром SDK (по конвенции payload.user_id)
- приёмка: wait_for_status (waitForStatus) вместо ручного цикла
- install py: @v0.1.2
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
13 hours ago
|
docs: приём s2s-доставки — тот же SDK (docs/09 §7)
...
Клиентские либы теперь двухсторонние: verify_webhook (py) /
WebhookVerifier::verify (php) — подпись, freshness, парс конверта;
v0.1.1 обеих либ.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
14 hours ago
|
| 2026-10-03 |
docs: гайд интеграции сервисов через клиентские библиотеки (docs/09)
...
Роли (админ регистрирует источник/ключ/типы, сервис шлёт через либу),
чеклист подключения, установки py/php, паттерны emit/send/batch/status
и когда какой, конвенции поля (priority, dedup, user_id), naming троек,
чеклист ревью после интеграции, приёмка через смоки и MCP, анти-паттерны.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
docs: рантбук под зафиксированные решения деплоя
...
docs/08: §0 — решения владельца (хост = целевая машина агента, домен
synapse.gnexus.space, телеграм отложен до #29); OAuth-клиент выделен в §5
(владелец регистрирует после подъёма, стек живёт и без него); эндпоинт
вебхука поправлен на POST /auth/webhooks. CLAUDE.md: закрытые вопросы
убраны, остался список открытых (#1 телеграм, #2 привязки каналов).
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
docs: клиентские библиотеки gn-synapse-client-{py,php}
...
docs/05 — раздел «Клиентские библиотеки», README — либы в quickstart,
CLAUDE.md — принятые решения: тонкие клиенты, env SYNAPSE_*,
установка через git.gnexus.space по тегу v0.1.0.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
fix: нав-бар использует знак Synapse (logoSrc у GnNavigationShell)
...
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
feat: собственный знак Synapse («Ветвление») вместо G-марки в PWA
...
Один источник → узел (роутинг, accent) → три канала. Палитра — токены
кита (secondary/accent/primary на плашке #16161E), геометрия — в грамматике
марки GNexus (viewBox 64, радиус 10, жирные штрихи). Ассеты: synapse-mark.svg
(favicon), PNG 512/192, maskable-512 (знак в безопасной зоне 80%), apple-touch
180. favicon в index.html теперь synapse-mark.svg, maskable-цель в manifest
указана на отдельный файл.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
docs: рантбук развёртывания для ИИ-агента (docs/08) + VAPID-ссылки
...
- docs/08: шаги деплоя (предполёт → .env+секреты → compose → nginx →
SSO smoke → VAPID → MCP приёмка → после деплоя), таблица переменных,
ловушки, откат. Открытые вопросы (хост/домен/тг-бот) — подтверждать
у владельца, не выбирать молча.
- docs/07: у settings_put — явная ссылка на рецепт VAPID в docs/06
(чтобы агент-деплоер его нашёл).
- docs/06: пара VAPID копируется в gnexus-creds; prod-путь — docs/08.
- README: список доков дополнен 06–08.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|

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: валидация payload.user_id и ретеншн (ttl_seconds, RETENTION_DAYS)
...
- Routing: _user_recipient() — субьектом user-канала принимается непустая
строка/число; массив/объект/bool → skipped-доставка с причиной (аудит),
а не «delivered в никуда».
- ttl_seconds стал действующим: воркер не маршрутизирует истёкшее событие
(новый статус expired, миграция 0005 пересоздаёт ck_event_status).
- Beat-задача synapse.expire_events (ежечасно): удаление событий с
истёкшим ttl и старше RETENTION_DAYS (дефолт 30; 0 — окно отключено),
deliveries каскадом.
- ui.js: бейдж expired; docs/05: «Ретеншн», формат значения payload.user_id.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
docs: фиксация отложенного вопроса привязки user_id к каналам (tg chat_id, push-подписка)
...
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
|
Спиннер загрузки (GnLoader circle), выравнивание и мелочи
...
- Лоадер во всех разделах — круговой спиннер кита (GnLoader circle) с
подписью «Загрузка…»: дефолтный вариант рисовал оранжевые квадраты с
жёстким hex (#FF3C00), мимо токенов кита.
- Дашборд: честный loading-флаг вместо трюка error === true, и лоадер
получает свой текст («Опрашиваю сервис…»); карточка «Каркас» заменена
на «С чего начать» — порядок настройки источников/целей/правил.
- Доставки: кнопка обновления центрируется точно по селекту (высота
слота 46px + компенсация margin-bottom группы кита), расхождение 2px.
- Аватар в футере дровер: убрана статус-точка «online» — она ничего не
говорит и не видна самому пользователю.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Редактирование правил и шаблон шапки страниц деталей
...
- RuleForm — общая форма правила (создание в списке и правка в карточке),
с пояснениями к весу, шумодаву, условиям и действиям.
- Правило редактируется на своей странице: дропдаун «Действия» →
«Редактировать», форма предзаполняется, PATCH атомарно.
- Единый шаблон шапки на всех страницах деталей: ссылка «назад» слева
над шапкой, переключатель с текстом («Включено»/«Включена»), правка и
удаление — в дропдауне, без кнопок у переключателя.
- Выравнивание иконки в .channel-hint / .form-note / .panel-note
(flex + align-items:center), чтобы не уезжала по базовой линии.
- Пояснения на страницах цели/правила: что такое config, что делают
условия и действия, сколько живёт шаблон.
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
|
Админка по правкам владельца: детали, тултипы, читаемость
...
- Навигация: иконки ph-pulse/ph-shapes не было в шрифте Phosphor
из кита -> activity/squares-four; пункт топбара подписан префиксно
(детали не теряют текущий раздел).
- Удаления в таблицах — btn-icon вместо полнокровных кнопок.
- Источники: таблица только slug/название/создан; ключи, типы и
описание — на странице источника, токен показывается один раз.
- Цели: страница цели, config как код с подсветкой (CodeBlock).
- Правила: страница правила — условия по одному ключу с подписями
и подсветкой, действия целиком, шаблон; «W» -> «Вес» и тултипы
кита во всех спорных местах (вес, шумодав, условия, статусы).
- Время везде HH:MM:SS, D месяца (год, если не текущий).
- Доставки: фильтры вынесены из шапки, с подписями Статус/Канал.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
compose: отключить healthcheck воркеру (HTTP-проверка не его случай)
...
HEALTHCHECK из Dockerfile стучится в :8000 — воркер HTTP не слушает,
вечный unhealthy в docker ps. Отключаем на уровне сервисa.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Админка SPA (#32): разделы Источники/Типы/Цели/Правила/Доставки
...
Маршруты + навигация в App.vue; ui.js — варианты статусов, формат
времени, JSON-обрезка. Токен ключа показывается один раз (модаль с
GnCopyButton), шумодав и switch вкл/выкл правила, s2s-target config в
JSON, фильтры доставок по статусу/каналу. Компоненты — из кита.
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
|

System-to-System (#28): подписанные доставки в системные webhooks
...
- app/signature.py: HMAC-подпись/проверка — зеркало WebhookSignature.php
gnexus-auth ("t=<unix>,v1=hmac_sha256('<t>.<body>', secret)"); verify —
константное сравнение + окно свежести 300 с (replay). Пригодится и для
входящих вебхуков gnexus-auth (single sign-out).
- app/worker/senders.py: POST в endpoint цели s2s с заголовками
X-Gnexus-Event-Id/Type/Timestamp/Signature + X-Synapse-Source; секрет
per-target (token_ref → S2S_SECRET_*), в БД только ссылка; 2xx = успех.
- tasks.py: первая попытка отправки сразу на маршрутизации (не ждать
beat-цикла), провал — общий ретрай-цикл; _attempt_send теперь одна.
- docs/05: отправка/ретраи/референс-код для принимающих сервисов;
.env.example: S2S_SECRET_*; README: структура, опечатка.
Проверено на живом стеке с приёмником-верификатором: OK (подпись/свежесть/
заголовки/тело), и цель без секрета — честная ошибка с именем переменной.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Routing Engine полный (#30): payload-матчинг, all/first, шумодав, ретраи
...
- условия правила: payload-матч (точный по top-level ключам, список = any-of)
- ROUTING_MATCH_MODE=all|first (дефолт all — применяются все подошедшие)
- шумодав: throttle_seconds у правила, окно на (channel,target), skipped-
записи с аудит-текстом; critical проходит всегда (договор docs/05)
- ретраи: beat (worker -B) + synapse.retry_due раз в минуту, паузы
30с/2м/10м/30м, 5 попыток -> failed; миграция 0003 (throttle_seconds
+ status skipped в CHECK)
- CLI: --payload и --throttle у add-rule; README/.env.example/docs/04/05
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
s2s: description источника — в реестре, не в теле вебхука
...
Уточнение к 3748cb5: description — атрибут записи источника в админке
Synapse, конверт события и s2s-доставка его не несут; получатель видит
имя в X-Synapse-Source.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
s2s: HMAC-подпись доставок (схема gnexus-auth) + description у источников/целей
...
- docs/05: раздел «Доставка s2s» — подпись t=<unix>,v1=hmac_sha256(secret,
"<t>.<body>") с константным сравнением и окном свежести, заголовки как у
gnexus-auth (X-Gnexus-Event-*) плюс X-Synapse-Source; секрет per-target,
в БД только token_ref; тело доставки несёт source_description.
- model/миграция: description (Text) у sources и channel_targets — кто это,
для ИИ-агента и человека, не гадать по названию.
- CLI: --description у create-source и add-target.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Ingestion (#31): приём событий по контракту docs/05
...
- POST /api/v1/events: Bearer-ключ -> source, анти-спуфинг (401/403/422),
дедуп по dedup_key в окне (DEDUP_WINDOW_SECONDS, дефолт 24ч), 202 мгновенно
- POST /api/v1/events/batch: честная частичная валидация по индексам,
202 при хоть одном принятом, dispatch строго после коммита
- GET /api/v1/events/{id}: статус + доставки, только своему источнику
- synapse.ingest: Routing Engine MVP — первое подошедшее правило по weight,
internal_log исполняется сразу (Jinja-рендер), каналы — #29/#28
- app/cli.py: create-source/create-key/add-type/add-target/add-rule для dev
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|