Хранитель доменных инвариантов — правил, которые НЕЛЬЗЯ нарушить ни при каком сценарии. Use when: проверка бизнес-правил, доменных ограничений, целостности данных, invariant check.
Проверяет, не нарушают ли изменения в коде абсолютные правила домена — условия, которые должны выполняться ВСЕГДА.
Инвариант — правило, нарушение которого означает, что система находится в невалидном состоянии. Допустить нарушение инварианта = создать баг, который невозможно исправить без потери данных.
| ID | Инвариант | Почему критичен |
|---|---|---|
| M1 | DM-канал между двумя пользователями уникален | Дубль = потеря истории, двойные уведомления |
| M2 | Порядок сообщений монотонен (seq только растёт) |
| Нарушение = перемешанная история |
| M3 | Курсор прочтения (read cursor) не идёт назад | Назад = повторные badge, ложные "непрочитанные" |
| M4 | Удалённое сообщение не возвращается | Возврат = утечка данных, нарушение GDPR |
| M5 | Отправитель = auth.uid() (RLS) | Подмена = спуфинг |
| M6 | Участник канала = тот, кто в members | Утечка контента |
| M7 | E2EE: ключи не уходят на сервер в plaintext | Нарушение = компрометация всей переписки |
| ID | Инвариант | Почему критичен |
|---|---|---|
| C1 | Участник звонка = подключён И имеет media track | Фантомный участник = сломанный UI |
| C2 | FSM: из ENDED нет перехода назад | Зомби-звонок |
| C3 | Один юзер = один активный звонок | Дублирование потоков |
| ID | Инвариант | Почему критичен |
|---|---|---|
| N1 | Push идёт ТОЛЬКО если получатель не онлайн | Дублирование = раздражение |
| N2 | Уведомление содержит real sender (не подменён) | Фишинг |
| N3 | Mute = полная тишина (0 push, 0 sound) | Нарушение = жалобы |
| ID | Инвариант | Почему критичен |
|---|---|---|
| A1 | Один email/телефон = один аккаунт | Дубль = путаница, security hole |
| A2 | OTP живёт ≤5 мин, 1 попытка | Replay attack |
| A3 | Session закрывается при logout со ВСЕХ устройств | Zombie session |
| ID | Инвариант | Почему критичен |
|---|---|---|
| F1 | Файл доступен ТОЛЬКО участнику канала | Утечка |
| F2 | Размер файла ≤ лимит ДО загрузки | DoS через storage |
| F3 | MIME проверяется серверной стороной | Content spoofing |
По файлам изменений определи, какие инварианты задействованы.
Для каждого:
| Уровень | Описание | Действие |
|---|---|---|
| 🔴 CRITICAL | Инвариант нарушен, данные под угрозой | STOP — не мержить |
| 🟠 HIGH | Инвариант защищён частично (только фронтенд) | Добавить серверную защиту |
| 🟡 MEDIUM | Инвариант защищён, но edge case не покрыт | Добавить обработку |
| 🟢 OK | Инвариант защищён на всех уровнях | — |
## Invariant Check — {модуль / изменение}
### Затронутые инварианты
- [M1] DM уникальность — 🟢 OK (UNIQUE constraint в БД)
- [M3] Read cursor монотонность — 🟠 HIGH (проверка только на фронте)
### Нарушения
1. [M3] Read cursor может пойти назад
Где: src/hooks/useMessages.ts:142
Причина: updateReadCursor не проверяет old > new
Защита: только клиентская
Рекомендация: добавить CHECK constraint или trigger в БД
### Рекомендации
- {конкретные шаги}