Как событие из любого модуля доходит до адресата: единый диспетчер
собирает событие, разводит его по двум моделям адресации и каналам,
журналирует доставку. Схема каналов, маршрутов и ленты —
в модели данных;
экран настроек —
в Admin Panel.
1
Диспетчер
источник → адресация → каналы → доставка
1
Источник
модуль зовёт диспетчер в core
2
Событие
строка notifications
3
Адресация
broadcast / targeted
4
Веер
маршрут × канал → deliveries
5
Доставка
in-app сразу + push · email/webhook в очередь
Источник поднимает событие, не шлёт сам
Модули не знают про email или Slack. При сбое, пороге или
governance-действии они зовут диспетчер в
core — тот пишет
строку
notifications
и дальше ведёт доставку сам. Рождение события и доставка — разные
заботы, циклов между модулями нет.
- Пишут: Harvester · Auth · Agent Engine · Knowledge Store · Admin — полный перечень в каталоге ниже.
- Строка: тип · severity · источник · ссылка на сущность ·
target_user_id (при targeted).
Тип события несёт свою модель адресации; диспетчер ветвится одним
условием — заполнен ли
target_user_id. Старые админские
типы всегда пишут
NULL — их путь нетронут.
Broadcast
target_user_id IS NULL
sync
security
budget
system
discovery
Targeted
target_user_id = X
Единственный адресат — конкретный пользователь (владелец агента,
субъект записи). Любая роль, включая member.
agent
account
Диспетчер берёт
notification_routes этого типа и на каждый
включённый маршрут заводит строку
notification_deliveries.
- Маршрут
enabled=true — иначе тип по этому каналу не уходит. Отдельного «выключателя типа» нет: погашены все маршруты — тип не идёт никому.
- Канал
enabled=false пропускается целиком — проверенный webhook держат про запас.
- Замок:
in_app × security и in_app × sync под locked=true — критичный тип нельзя обнулить, он всегда виден в ленте.
- Личные каналы (in-app, email) ещё сверяются с настройками адресата.
in-appпишется сразу и шлёт сигнал в
pub/sub
— колокольчик обновляется мгновенно (push); до клиента сигнал
доезжает по платформенному
потоку живых обновлений.
email · webhookв очередь
SAQ
— ретраи и тротлинг, чтобы всплеск не забил доставку.
Адресаты не хранятся списком — диспетчер вычисляет их на лету и снимает
тех, кто отписался.
- Broadcast → все
users с ролью owner / admin, динамический срез status = active, не ручной перечень.
- Targeted → один адресат,
target_user_id.
- Личный opt-out: по каждому сверяется
notification_prefs (in_app_enabled, email_enabled) по типу; строки нет — дефолт каталога (у личных типов email выключен, opt-in).
- Замков на личном уровне нет — отписаться можно от любого типа; организационный замок держит in-app
security / sync лишь на уровне организации (что нельзя выключить).
- Webhook персон не имеет — общий endpoint команды; личные настройки не влияют, targeted по нему не идёт.
Дедуп и тротлинг
Однотипные события в окне схлопываются в одно уведомление с растущим
счётчиком — не
N писем на каждую неудачу.
- Ключ дедупа: конкретное событие (не категория
event_type) + затронутая сущность (source_ref); для targeted — плюс адресат.
- Раздельно: «источник отвалился» и «серия сбоев» остаются разными уведомлениями.
- Окно и пороги в v1 фиксированные; тонкая настройка на инстанс — v2.
Payload вебхука — по пресету канала
→ Каналы
preset канала выбирает форму тела POST: Slack получает свой
формат сообщения, generic — нейтральный JSON для любого приёмника (свой
бот, n8n, Teams через Power Automate Workflow):
{
// generic preset — нейтральный JSON
"event": "sync.failure_series",
"severity": "critical",
"title": "Источник Jira: 5 сбоев подряд",
"source": "harvester",
"source_ref": "source/42",
"ts": "2026-05-27T14:30:00Z"
}
- Endpoint и secret (
url_enc, secret_enc) — write-only: в UI и export не возвращаются.
- «Test» шлёт это же тело с фиктивным событием.
- Только broadcast идёт по вебхукам — targeted-события личные, каналом команды не расходятся.
2
Каталог событий
что поднимает уведомление · по типам
Админ события
broadcast → администраторам
| Событие |
Источник |
Severity |
Условие |
| Sync |
| Серия сбоев прогона |
harvester |
critical |
несколько sync_runs.state=failed подряд по источнику |
| Источник отвалился |
harvester |
critical |
проба источника → last_probe_status ≠ ok (health error) |
| Прогон с потерями |
harvester |
warning |
succeeded при error_count > 0 (часть в DLQ) |
| DLQ накапливается v2 |
harvester |
warning |
хвост dead-letter растёт сверх порога |
| Security |
| Перебор пароля |
auth |
critical |
≥ 10 неудачных входов на аккаунт / 15 мин |
| Смена ролей |
auth |
critical |
изменён users.role (owner / admin / member) |
| API-ключ истекает |
auth |
warning |
за 7 дней до expires_at |
| Последний Owner v2 |
auth |
warning |
в системе остался один Owner — риск блокировки |
| Budget |
| Порог расходов AI |
admin |
critical |
SUM(cost) за месяц > ai_monthly_budget |
| System |
| Провайдер недоступен |
admin |
critical |
проверка соединения провайдера → error (здоровье, не расход) |
| Сервис degraded / down v2 |
admin |
critical |
postgres / redis / n8n не отвечают штатно |
| Backup провалился |
knowledge_store |
critical |
плановый снимок KS завершился ошибкой |
| Curation Pass упал |
knowledge_store |
warning |
curation_runs.state=failed |
| Переэмбеддинг затянулся v2 |
knowledge_store |
warning |
прогон после смены модели дольше ожидаемого |
| Storage у предела v2 |
admin |
warning |
PG / pgvector приблизились к лимиту |
| Discovery |
| Новый контейнер v2 |
harvester |
info |
объект за пределами разрешённого scope источника — с действием «в scope» |
Общие события
targeted → пользователю
| Событие |
Источник |
Severity |
Условие |
| Agent · targeted → владелец · governance |
| Прогон провалился |
agent_engine |
warning |
agent_runs.state=failed (reason error / iteration_cap / stale) |
| Недельный бюджет исчерпан |
agent_engine |
info |
agent_runs.state=skipped, reason=budget_exceeded |
| Агент приостановлен админом |
agent_engine |
info |
agents.admin_paused → true |
| Модель агента снята |
agent_engine |
warning |
agents.model_id → NULL (модель убрана из списка) |
| Прогон завершён успешно v2 |
agent_engine |
info |
доставка выхода прогона — отложена вместе с доставкой в чат-тред |
| Account · targeted → пользователь |
| Роль изменена админом |
auth |
info |
audit_log смены роли (meta old→new) |
| Выдан временный пароль |
auth |
warning |
users.must_change_password → true (сброс админом) |