Первая постановка на уведомления

This commit is contained in:
mi
2026-07-26 13:20:23 +03:00
parent 8c3c454998
commit 8822ae609f
15 changed files with 698 additions and 271 deletions
+493
View File
@@ -0,0 +1,493 @@
# Бизнес-постановка: Уведомления (Notification Center)
**Статус:** бизнес-постановка согласована (v3: D1–D13 закрыты) → вход в проектирование arch / OpenAPI / модули
**Продукт:** HAN Chat (клиентское приложение + `api-backend`)
**Источники:** макет Figma (**не канон** — только визуализация элементов UI, где применимо; при расхождении приоритет у этого ТЗ), черновик требований, архитектура `HAN_chat_specification`
**Связанный backlog:** «Моделирование уведомлений»; смежно — кнопка «Позвонить оператору», кнопка «Войти»; **отдельно** — индикатор непрочитанных сообщений чата (не в scope этого ТЗ)
---
## 1. Цель
Дать клиенту единый канал сервисных и маркетинговых коммуникаций вне чата: срочные напоминания, статусы услуг, документы, оплаты, акции, а также подсказки в гостевой зоне (авторизация, установка PWA).
Чат остаётся каналом диалога с оператором; уведомления — канал «компания → клиент» с действиями (переход, оплата, загрузка/скачивание документов, переход в чат по акции). Непрочитанные ответы оператора в чате **не** моделируются как уведомления (см. §3.2).
Гостевые `ads`/`promo` — не персонализированная реклама на клиента; отдельное маркетинговое согласие не требуется. Функционал описывается в Пользовательском соглашении.
---
## 2. Два контура показа
| Контур | Кто видит | Источник данных | Frontend |
|---|---|---|---|
| **G. Гостевые уведомления** | Только гость | Таблица гостевых уведомлений в App DB → **public API** | Единственный источник в гостевом режиме |
| **P. Персональные уведомления** | Только авторизованный | Таблица персональных (`user_id` NOT NULL) → **JWT API** | Единственный источник в ЛК |
Правила:
1. До авторизации: только контур **G** (ответ public API as-is).
2. После авторизации: только контур **P**. Гостевые **не** показываются и **не** переносятся в персональные.
3. Смешивать G и P в одной таблице / через `user_id IS NULL`**запрещено**.
4. В v1 **нет** клиентских условий показа (ОС / PWA / прочие фильтры): фронт показывает список из API в рамках лимита и сортировки. Локальная генерация карточек на устройстве **запрещена**.
5. У фронта всегда ровно один источник: public API **или** JWT API.
Дубли тематики `ads`/`promo` в G и P допустимы; после входа виден только P.
---
## 3. Границы релиза
### 3.1. В scope
- Контуры G и P (§2); персональный жизненный цикл: `lifecycle_status` / `visibility` / `is_read` / `close_reason` + `record_status` (arch-05).
- UI: карусель на главной, Центр уведомлений, деталка (где применимо).
- Каталог типов; публичный id — **UUID v7** (генерация в приложении).
- Клиентские действия (ЛК): крестик, CTA, деталка/оплата, done/got it, документы (`chat.attachments.*`).
- Internal API: **Create**, **Cancel** (без upsert / без update content) — §7.4.
- Константы `notification.*` в `app_settings`; вложения — reuse `chat.attachments.*`.
- Кнопки **Чат** + **Оператор** (`tel:``operator.call.phone`).
- Готовность модели к Web Push (UUID, события); реализация push — out of scope.
### 3.2. Вне scope
- Тип `message` / непрочитанный чат (отдельная фича).
- Push / Web Push / deep link из push.
- Админ-UI для гостевых уведомлений (v1: insert/SQL).
- Жёсткая привязка «тип ↔ upstream-сервис».
- Профиль «Документы» / архив оплат — не заменяются уведомлениями.
- Запись в `sync_queue` из application-кода: только **триггер БД** + новый `task_type`; куда в Bitrix — ТЗ `bitrix-sync`.
- SMS/email поверх ЛК.
- История чатов как UI-раздел — deprecated; backend API диалогов этим ТЗ не удаляется.
- **Архив `lifecycle_status = N` в UI v1 — нет.**
- Поле `opened` / impression — **не моделируем** (CTA → `is_read`).
---
## 4. Изменения UI
| Место | Было | Стало |
|---|---|---|
| Под полем чата | (см. текущий UI) | **Чат** + **Оператор** (`operator.call.phone`) |
| Верхнее меню | История чата | Центр уведомлений (колокольчик; бейдж только в ЛК) |
| Главная | — | Карусель (лимит/сортировка §6.1) |
| Центр | — | ЛК — список; гость — auth-gate + «Авторизоваться» |
| Деталка | — | Для типов с деталкой; гостевые `ads`/`promo`**без** деталки |
Визуал типов — по Figma, где применимо. Figma не канон поведения.
---
## 5. Бизнес-модель
### 5.1. Слои контента
1. **Краткий** — баннер и строка списка: `header`, `text`, цена, CTA.
2. **Детальный**`details.*`, документы, кнопки. Только если у типа есть деталка.
### 5.2. Справочник типов (**принято**)
| Слой | Где | Содержимое |
|---|---|---|
| Структурный | App DB `notification_types` | `code`, `priority`, `countable`, признак «есть деталка», `record_status` |
| Визуал | Frontend (тема/константы), сверка с Figma | Цвет, иконка, оформление |
| Тексты по умолчанию | `text_resources` | `label`, дефолтный CTA, empty states |
В экземпляре **не** дублируются label/цвет/icon/countable/priority/action — только `notification_type` (FK/code).
### 5.3. Каталог типов
| type | label | priority | countable | Action по CTA | Контур |
|---|---|---|---|---|---|
| `authorize` | Гостевой режим | 2 | нет | Сценарий авторизации | G |
| `install_app` | Приложение | 2 | нет | Адаптивная установка PWA (§5.7) | G |
| `urgent` | Срочно | **1** | да | Детальный просмотр | P |
| `reminder` | Напоминание | 2 | да | Детальный просмотр | P |
| `news` | Новость | 2 | да | Детальный просмотр | P |
| `ads` | Предложение | 2 | да* | G: чат после auth; P: чат + закрытие (§7.2) | G и/или P |
| `promo` | Акция | 2 | да* | G: чат после auth; P: чат + закрытие (§7.2) | G и/или P |
| `docs_required` | Требуются документы | 2 | да | Деталка (+ загрузка) | P |
| `docs_ready` | Документы готовы | 2 | да | Деталка (+ скачивание) | P |
| `status_changed` | Статус | 2 | да | Детальный просмотр | P |
| `payment_pending` | Оплата | 2 | да | Переход по `payment_url` | P |
\* Countable/read применяются только в контуре **P**. В **G** read-state нет.
Типы `install_Android` / `install_IOS-HarmonyOS` / `message`**отсутствуют**.
`priority`: меньше = важнее. Seed: `urgent = 1`, остальные = `2`.
CTA по умолчанию:
- `authorize` — «Войти →»
- `install_app` — «Установить →» (на UI инструкции текст может быть «Как установить →»)
- `urgent` / `reminder` / `news` / `status_changed` — «Подробнее →»
- `ads` / `promo` — «Узнать подробнее →»
- `docs_required` — «Загрузить документы →»
- `docs_ready` — «Скачать →»
- `payment_pending` — «Оплатить →»
**Инвариант:** CTA у `docs_required` / `docs_ready` открывает **карточку**; upload/download — на карточке.
**Инвариант:** жёсткой привязки «сервис X → тип Y» нет.
### 5.4. Поля экземпляра (контур P)
| Поле | Обяз. | Описание |
|---|---|---|
| `id` | да | UUID v7 (api-backend при Create) |
| `user_id` | да | `UserIdentity.id` NOT NULL |
| `notification_type` | да | Код из справочника |
| `notification_datetime` | да | UTC |
| `header` | да | Заголовок |
| `text` | нет | Подзаголовок |
Опционально:
| Поле | Описание |
|---|---|
| `date_expired` | → `lifecycle_status=N`, `close_reason=expired` |
| `price` | Текущая цена (₽), опционально. Может быть без `old_price` |
| `old_price` | Старая цена (₽), опционально. Имеет смысл только вместе с `price` (зачёркнутая «было») |
| `payment_url` | Обязателен для `payment_pending` |
| `details` / `details.*` | Деталка (если нужна) |
| `details.deadline` | Срок на карточке |
| `send_documents` | UI загрузки клиентом |
| `documents[]` | Вложения компании в бакете **`han-chat-attachments`** (id объектов working S3) |
| `chat_message_text` | Текст в чат при CTA (`ads`/`promo` P — **обязателен**) |
| `button_done` / `button_gotit` / `button_gotit_text` | Кнопки деталки |
| `external_id` + `source` | Идемпотентность Create (§8) |
### 5.5. Состояние экземпляра (контур P)
| Атрибут | Значения | Семантика |
|---|---|---|
| `record_status` | `A` / `D` | Soft-delete строки (arch-05) |
| `lifecycle_status` | `A` / `N` | `N` — нет в UI; в БД для аудита |
| `visibility` | `V` / `I` | `I` — нет на главной, есть в Центре (пока `lifecycle_status=A`) |
| `is_read` | да / нет | Для countable; бейдж = `A` ∧ countable ∧ ¬is_read |
| `close_reason` | см. ниже | При `A``N` |
| `close_reason` | Когда |
|---|---|
| `user_done` | «Выполнено»; успех upload `docs_required`; Cancel после успешной оплаты |
| `expired` | `date_expired` |
| `cancelled` | Cancel (отзыв системой) |
**`is_read``visibility`:** прочтение → бейдж; видимость → главная. Правила — §7.1.
Контур G: per-user lifecycle / visibility / is_read **нет**.
### 5.6. Константы и мнемоники
`notification.*` в `app_settings`. Вложения — ключи чата:
- `chat.attachments.allowed_extensions`
- `chat.attachments.allowed_mime_types`
- `chat.attachments.disallowed_extensions`
- `chat.attachments.max_size_mb`
- `chat.attachments.presigned_upload_ttl_seconds`
Оператор: **`operator.call.phone`**.
Seed `notification.*`:
| Ключ | Смысл | Default |
|---|---|---|
| `notification.gotit.default_ttl_days` | Got it без `date_expired` → now+N дней | `3` |
| `notification.docs_ready.default_ttl_days` | После скачивания документов, если `date_expired` пуст → now+N дней | `3` |
| `notification.home.max_items` | Лимит карусели | `15` |
| `notification.center.max_items` | Лимит Центра (ЛК) | `15` |
| `notification.carousel.autoplay_enabled` | `Y`/`N` | `N` |
| `notification.carousel.autoplay_interval_ms` | Интервал | `5000` |
Мнемоники UI (`text_resources`) — **принято**:
- `notification.center.empty.title` / `.text` — пустой Центр (ЛК)
- `notification.guest_center.title` / `.text` / `.cta` — гостевой Центр
- `notification.detail.not_found.title` / `.text` — 404 деталки («На главную»)
API при чужом/`N`/отсутствии → **404**; UI — нейтрально «Уведомление недоступно».
### 5.7. Гостевые уведомления (контур G)
- Одна таблица гостевых уведомлений; `record_status` + актуальность **`A`/`N`** (без per-user state).
- Public API отдаёт все активные (`A`); фронт **не фильтрует** по ОС/PWA в v1.
- Наполнение v1 — insert/SQL; public API — read-only.
- Типы в G: `authorize`, `install_app`, `ads`, `promo` (и при необходимости другие broadcast-типы позже).
- Крестика нет. Деталки у `ads`/`promo` в G **нет**.
**`install_app` — один тип, адаптивный CTA (принято, технически возможно):**
1. Пользователь нажал CTA.
2. Если среда поддерживает установку PWA (`beforeinstallprompt` / эквивалент) — запускаем установку.
3. Иначе — показываем инструкцию установки (локальный экран/модалка на фронте; отдельный тип в API не нужен).
Fallback (если адаптивный CTA окажется нереализуем на конкретной платформе сборки): две записи в G (`install` для Android-потока и для iOS/Harmony) — только как запасной план реализации, не целевая модель данных.
**CTA прочих G:**
- `authorize` → сценарий авторизации.
- `ads` / `promo` → как «популярные вопросы»: старт auth; после успеха в чат уходит `chat_message_text` кампании от лица клиента. Кампания для других гостей остаётся `A`.
### 5.8. Персональные (контур P) — кратко
Создаются Internal **Create**; полная модель §5.4–5.5. Поведение CTA — §7.
---
## 6. Поведение UI
### 6.1. Главная (карусель)
**ЛК (P):**
- Выборка: `record_status=A`, `lifecycle_status=A`, `visibility=V`.
- Лимит `notification.home.max_items` (15).
- Сортировка: `priority` ↑, затем `notification_datetime` ↓.
- Крестик → `visibility=I` (синхрон на все устройства).
- Карусель: свайп; автопрокрутка по settings.
- CTA → action типа + эффекты §7.1.
**Гость (G):**
- Список = ответ public API; лимит 15; сортировка: `priority` ↑, затем datetime ↓ (поля сортировки — в таблице G / типе).
- Крестика нет. Один источник — public API.
### 6.2. Центр
**Гость:** колокольчик без бейджа; экран auth-gate + «Авторизоваться» (мнемоники §5.6). Списка нет.
**ЛК:**
- `record_status=A`, `lifecycle_status=A` (в т.ч. `visibility=I`), лимит 15, без архива `N`.
- Точка на непрочитанных countable.
- Сортировка: `priority` ↑ → непрочитанные выше → datetime ↓.
- Бейдж = непрочитанные countable (`lifecycle_status=A`); sync через бэкенд.
- Empty state — мнемоники §5.6.
### 6.3. Деталка (контур P; инструкция `install_app` — локальный UI)
- SPA: `/notification/{uuid}`.
- Чужой / `N` / deleted → **404** (не 403).
- Upload: quarantine → Message Safety → working (`han-chat-attachments`) → **триггер БД**`sync_queue`.
- Company `documents[]`: тот же бакет **`han-chat-attachments`**; preview/download + audit как в чате.
- `button_done``lifecycle_status=N`, `close_reason=user_done`.
- `button_gotit``visibility=I`; при пустом `date_expired` — now + `notification.gotit.default_ttl_days`; `is_read=true` (got it = действие пользователя, см. §7.1).
- Обе кнопки допустимы одновременно; исход по нажатой. Обе false → только back.
### 6.4. Завершение по типам (P)
| Тип | → `lifecycle_status=N` |
|---|---|
| `payment_pending` | Cancel после callback оплаты, `close_reason=user_done` |
| `docs_required` | Успешный upload → `user_done` |
| Другой тип с `send_documents` | Upload сам не закрывает |
| `ads` / `promo` | CTA → `N` (§7.2) |
| `status_changed` | «Выполнено»; с главной уходит уже по CTA (`visibility=I`) |
| `docs_ready` | После скачивания — `I` + TTL/`date_expired` (§7.1); в `N` уходит по expire (или Cancel/done) |
| Прочие | done / expired / cancelled |
---
## 7. Матрицы поведения (контур P)
### 7.1. `is_read` и `visibility` — раздельно
**Правило прочтения (единое):**
> Любое **CTA** → `is_read = true` (идемпотентно).
CTA = первичное действие карточки: кнопка CTA на баннере, тап по строке Центра ведущий к action типа, «Оплатить», переход в деталку по CTA, отправка в чат по ads/promo.
Не CTA: системный back, крестик (крестик не ставит `is_read`).
**Матрица `visibility` / `lifecycle` (избирательно):**
| Тип | Событие | `is_read` | `visibility` | `lifecycle_status` |
|---|---|---|---|---|
| любой | Крестик на главной | — | `I` | — |
| любой с `button_gotit` | Got it | `true` | `I` (+ TTL если нужно) | — |
| любой с `button_done` | «Выполнено» | `true` | — | `N` / `user_done` |
| `urgent` | CTA → деталка | `true` | — | — |
| `reminder` | CTA → деталка | `true` | — | — |
| `status_changed` | CTA → деталка | `true` | `I` | — |
| `news` | CTA → деталка | `true` | `I` | — |
| `ads` / `promo` | CTA (чат) | `true` | `I` | `N` (+ сообщение в чат) |
| `docs_required` | CTA → деталка | `true` | — | — |
| `docs_required` | Успешный upload | `true` | — | `N` / `user_done` |
| `docs_ready` | CTA → деталка | `true` | — | — |
| `docs_ready` | Скачивание документов | `true` | `I` | — (+ TTL, см. ниже) |
| `payment_pending` | CTA «Оплатить» | `true` | `I` | — (далее Cancel→`N` при оплате) |
Пояснения:
- Прочерк «—» = поле этим событием не меняется.
- Для `ads`/`promo`: одновременно `is_read`, `visibility=I` и `lifecycle_status=N`. В UI запись исчезает из-за `N`; `I` фиксирует намерение «убрать с главной» на случай гонок/аудита.
- Открытие деталки из Центра по тапу строки = CTA → всегда `is_read=true`; `visibility` меняется только если тип/событие есть в матрице выше.
- `status_changed`: CTA → деталка сразу уводит карточку с главной (`visibility=I`); в Центре остаётся, пока `lifecycle_status=A`.
- `docs_ready`, скачивание документов: `is_read=true`, `visibility=I`; если `date_expired` пуст — выставить `date_expired = now + notification.docs_ready.default_ttl_days` (default 3). Если `date_expired` уже задан продюсером — **не перезаписывать**. Далее сработает общий expired-job → `lifecycle_status=N`, `close_reason=expired`.
### 7.2. `ads` / `promo` (P)
1. CTA отправляет в чат `chat_message_text` (обязательное поле).
2. `is_read=true`, `visibility=I`, `lifecycle_status=N`.
3. Деталки у персональных ads/promo на MVP **нет** (симметрично G).
### 7.3. Валидация Internal Create (**принято**)
| Тип | Обязательно | Не ожидается | Примечания |
|---|---|---|---|
| `urgent`, `reminder`, `news`, `status_changed` | `header`; `details` если нужна деталка | `payment_url` | `button_*` по необходимости |
| `ads`, `promo` | `header`; **`chat_message_text`** | `payment_url` | `price` и `old_price` — разные опц. поля; `price` без `old_price` допустим; `old_price` без `price` — нет; деталки нет |
| `docs_required` | `header`; `send_documents=true` | `payment_url` | |
| `docs_ready` | `header`; `documents[]` ≥1 | `payment_url` | |
| `payment_pending` | `header`; `payment_url` | — | `price` **не** обязателен; `details` **разрешены** |
| любой | `user_id`, `notification_type`, `source`, `external_id` | неизвестный тип / user | |
Ошибка валидации → `400` (OpenAPI).
Типы контура G через Internal Create **не** создаются (только insert/public table).
### 7.4. Контракт продюсера
| Операция | Семантика |
|---|---|
| **Create** | Создать новое уведомление. Ключ `source`+`external_id`: если уже есть активная (`lifecycle_status=A`) запись с той же парой → **409 Conflict** (контент не обновляется). |
| **Cancel** | `lifecycle_status=N` + `close_reason` (`cancelled` \| `user_done`). |
| **Смена контента** | Upsert / update content **нет**. Сценарий: **Cancel** старого → **Create** нового (новый `external_id` или та же пара после `N` — допустим Create, т.к. активной записи с ключом больше нет; политика ключа после `N` — в OpenAPI: разрешить Create с тем же ключом только если нет активной). |
Кто закрывает:
| Сценарий | Кто | reason |
|---|---|---|
| Оплата успешна | Внешний → Cancel | `user_done` |
| Upload `docs_required` | api-backend | `user_done` |
| CTA ads/promo | api-backend по действию клиента | (закрытие как `user_done` или отдельный reason — по OpenAPI; default `user_done`) |
| «Выполнено» | Клиент | `user_done` |
| Отзыв | Внешний → Cancel | `cancelled` |
| TTL | Scheduler | `expired` |
---
## 8. Идентификация
- Id P и G — **UUID v7** (генерация приложением / seed-скриптом, не клиентом).
- SPA: `/notification/{uuid}` (только P; гостевая инструкция install — локальный роут без обязательного id в API).
- Deep link — с push (later).
- Идемпотентность Create — §7.4 (без upsert).
---
## 9. API
### 9.1. Клиентские (JWT) — контур P
Список / get one / hide / mark-read·done·gotit / upload / counter — по §6–§7.
Владелец = `user_id` из JWT. Чужое/`N` → 404.
### 9.2. Public — контур G
`GET` списка активных гостевых уведомлений (без JWT).
### 9.3. Internal
- Path: **`/internal/notifications/v1/...`**
- Service token — как у прочих internal (arch-02).
- Callers: сервисы приватной сети облака (в т.ч. другие ВМ). Интернет — нельзя.
- Операции: **Create**, **Cancel** только.
### 9.4. Realtime (**подписка**)
Желательно в релизе, иначе polling.
После connect на `WS /api/v1/realtime` клиент выполняет явный **`subscribe` на уведомления** (например `subscribe: { notifications: true }`), отдельно от `dialog_ids`.
События: `notification.created` / `notification.updated` / `notification.closed`.
Reconnect → reconciliation GET списком. Гостю WS для G не требуется.
### 9.5. Готовность к Web Push
Стабильный UUID v7; события жизненного цикла; `header`/`text` для текста push; path `/notification/{uuid}`. Реализация push — вне scope.
---
## 10. Смежные сервисы
| Компонент | Изменение |
|---|---|
| **Frontend** | Один источник (G или P); карусель; Центр; адаптивный `install_app`; Чат/Оператор |
| **api-backend** | Модель G+P; public/JWT/internal; scheduler expired; WS subscribe notifications; upload |
| **App DB** | Две таблицы; types+priority; триггер → `sync_queue`; seed |
| **nginx** | Internal не в интернет; rate limit; SPA |
| **Redis/WS** | События notification.* по подписке |
| **message-safety** | Как chat attachments |
| **bitrix-sync** | Новый task_type |
| **S3** | Бакет вложений уведомлений (клиент и компания): **`han-chat-attachments`** |
| **settings** | `notification.*`, `chat.attachments.*`, `operator.call.phone`, мнемоники |
Порядок: (1) модель + API + UI → (2) Internal Create/Cancel + seed G → (3) документы + триггер → (4) WS subscribe.
---
## 11. Критерии приёмки
1. Колокольчик; ЛК — бейдж непрочитанных, sync `is_read`/`visibility` между устройствами; гость — без бейджа, Центр = «Авторизоваться».
2. ЛК главная: лимит 15, sort priority→datetime, только `A`+`V`; крестик → `I`; `N` не в UI.
3. Гость: только public API, без клиентских фильтров ОС/PWA; один тип `install_app` с адаптивным CTA.
4. Типы каталога отличимы; `message` нет; после логина только P.
5. Деталка/кнопки по флагам; docs_* CTA → карточка; ads/promo P: чат + `is_read`+`I`+`N`.
6. Internal только Create/Cancel; дубликат активного `source`+`external_id` → 409; смена контента = Cancel+Create.
7. Expired → `N`, UI исчезает.
8. Upload → quarantine → working → триггер → `sync_queue`; company docs в `han-chat-attachments`.
9. Оператор — только `operator.call.phone`.
10. Константы из `app_settings`; UUID v7; 404 на чужое/`N`.
11. Валидация Create по §7.3.
12. WS: события после `subscribe` на notifications.
13. Push не обязателен; модель push-ready.
14. Архива `N` нет; поля `opened` нет.
---
## 12. Решения D1–D13 (закрыты)
| # | Решение |
|---|---|
| **D1** | Два контура: **G** (всё через public API) и **P** (JWT). Без локальных карточек и без условий показа в v1. `install_app` — один тип, адаптивный CTA (установка / инструкция). Fallback — два баннера в данных, не целевая модель. |
| **D2** | Схема справочника §5.2 принята. |
| **D3** | Мнемоники empty/404 и поведение 404 приняты (§5.6). |
| **D4** | ads/promo CTA: `is_read` + `visibility=I` + `lifecycle_status=N` + чат. |
| **D5** | Любое CTA → `is_read=true`. `visibility` — только по матрице §7.1. |
| **D6** | Валидация §7.3: `chat_message_text` обязателен для ads/promo; `price` у payment необязателен; `details` у payment разрешены. |
| **D7** | `/internal/notifications/v1/...` + service token. |
| **D8** | WS: явная подписка на notifications. |
| **D9** | Бакет company/client файлов уведомлений: `han-chat-attachments`. |
| **D10** | `opened` / impression удалены; достаточно `is_read` от CTA. |
| **D11** | Upsert нет; механика **Cancel + Create**; конфликт активного ключа → 409. |
| **D12** | IDOR: единый 404. |
| **D13** | Одновременные done+gotit допустимы без доп. правил продюсеру. |
---
## 13. История решений (Q)
| # | Решение |
|---|---|
| **Q1** | Непрочитанный чат / `message` — вне ТЗ. |
| **Q2** | `N` в БД, не в UI; архива v1 нет. |
| **Q3** | `payment_pending`: CTA «Оплатить» → `is_read`+`I`; деталка не обязательна. |
| **Q4** | Гостевые — отдельная таблица; insert v1. |
| **Q5** | Документы через S3; клиент quarantine→working. |
| **Q6** | `sync_queue` только триггером БД. |
| **Q7** | Лимит 15; sort priority→datetime. |
| **Q8** | Подсказки установки/входа — в контуре G через API (не локальный FE). |
| **Q9** | UUID v7; `/notification/{uuid}`. |
| **Q10** | `message` исключён. |
| **Q11** | Владелец = `user_id`. |
| **Q12** | `record_status``lifecycle_status`. |
| **Q13** | `operator.call.phone`. |
| **Q14** | Got it = `I` (+TTL), не обязательно `N`. |
| **Q15** | Два контура G/P (вместо A/B/C). |
| **Q16** | Гостевой Центр = auth-gate. |
| **Q17** | Reuse `chat.attachments.*`. |
| **Q18** | Поле `opened` снято (D10). |
| **Q19** | Модель push-ready; push вне scope. |
---