Files
han-app/functional_blocks (business logic)/chat-requirements.md
T

38 KiB
Raw Blame History

Бизнес-постановка: Обмен сообщениями (Chat / Dialog)

Статус: v1 — консолидация принятых решений из HAN_chat_specification (arch-00arch-05, module-01, module-05, module-06); открытые вопросы зафиксированы в §17
Продукт: HAN Chat (клиентское приложение + api-backend + message-safety + bitrix-local-app / Open Lines)
Источники: архитектура HAN_chat_specification; макет Figma (не канон — только визуализация; при расхождении приоритет у этого ТЗ и arch-документов)
Связанный backlog: непрочитанные сообщения на кнопке «Чат» (синхронизация между устройствами); UX ошибки отложенного сообщения после входа
Смежно: пользователь (auth/bootstrap), уведомления (send_chat_message, кнопки Чат/Оператор), популярные вопросы, Message Safety

Нормативная часть — §1–§16. §17 — ненормативный журнал решений и открытых вопросов; при расхождении с §1–§16 приоритет у §1–§16. При расхождении этого документа с arch/module после их обновления — приоритет у arch/module до синхронизации.


1. Цель

Дать клиенту канал диалога с оператором компании: отправка текста и файлов из приложения в Bitrix24 Open Lines и получение ответов оператора в реальном времени — с проверкой исходящих сообщений (Message Safety), идемпотентностью, ownership и деградацией без потери уже принятых данных.

Чат — канал «клиент ↔ оператор». Уведомления — канал «компания → клиент» вне чата (см. notification-requirements.md). Непрочитанные ответы оператора не моделируются как уведомления; индикатор непрочитанного чата — отдельная фича (§3.2, §17.2).


2. Два направления обмена

Направление sender_type Кто инициирует Проверка Message Safety Доставка
C→O. Клиент → оператор client Frontend (JWT) Обязательна до Open Lines api-backendbitrix-local-app → Open Lines
O→C. Оператор → клиент company Bitrix24 webhook → bitrix-local-app → inbox API Нет outbound moderation (доверенный канал); MIME/size/antivirus policy модуля App DB + WS / polling

Правила:

  1. Чат доступен только авторизованному клиенту (JWT + bootstrap). Гость инициирует auth; текст сохраняется локально и отправляется после входа (§6.4).
  2. У пользователя не более одного активного диалога (open | waiting_for_company | waiting_for_client).
  3. dialog_id приложения равен external_chat_id для Open Lines.
  4. MVP: одно исходящее сообщение — либо текст, либо ровно один файл (content_kind), не оба сразу.
  5. Клиент на POST .../messages получает только финальный результат (или ошибку инфраструктуры), не промежуточное «обрабатывается».
  6. Источник истины ленты — App DB; WS — at-most-once best effort; после reconnect — REST reconcile.

3. Границы релиза

3.1. В scope

  • Один активный диалог на пользователя; ленивое создание перед первым сообщением.
  • Исходящие: content_kind text | file; входящие: текст и файлы оператора.
  • Orchestration Message Safety: sync check + sync-wait poll task_id + checkpoint safety_tasks + recovery.
  • Presigned upload клиента в S3-quarantine → complete → safety → promote в S3-data attachments.
  • Durable outbox App → Open Lines; idempotent delivery по message_id.
  • Inbox Open Lines → App: message.new, dialog.closed; idempotency по (external_chat_id, bitrix_message_id) / event_id.
  • Realtime WS /api/v1/realtime + polling fallback истории сообщений.
  • Популярные вопросы как обычная отправка текста после auth.
  • Общий механизм отложенного сообщения на frontend (ручной ввод, популярный вопрос, CTA уведомлений send_chat_message).
  • Кнопки UI «Чат» и «Оператор» (tel:operator.call.phone) — смежно с уведомлениями.
  • Нормализация текста входящих из Bitrix (удаление служебной разметки отправителя) — уже закрытый дефект backlog.
  • Ownership, rate limits, Idempotency-Key (TTL 24 ч) на create dialog / send message.
  • Константы chat.attachments.*, rate_limit.message_send.*, operator.call.phone.

3.2. Вне scope

  • Смешанное сообщение «текст + файл(ы)» (post-MVP, отдельная версия API).
  • Несколько вложений в одном исходящем сообщении.
  • UI-раздел «История чатов / список диалогов» как продуктовый экран — deprecated для MVP frontend (один чат с компанией); backend GET /api/v1/dialogs не удаляется.
  • Индикатор непрочитанных сообщений чата и sync между устройствами (Dialog.client_last_opened_at и аналоги) — backlog п.2324; не путать с бейджем уведомлений.
  • Typing indicators, реакции, редактирование/удаление сообщений клиентом, ответы на конкретное сообщение (quote/reply).
  • Голосовые / видеосообщения, стикеры, произвольные форматы сверх chat.attachments.*.
  • Push / deep link в чат (модель может быть push-ready позже).
  • Админ-модерация очереди needs_review (enum зарезервирован, в MVP не создаётся).
  • CRM Contact sync в hot path чата (bitrix-sync не участвует в Open Lines delivery).
  • Реальная антивирус/LLM-модерация: в MVP допускается stub message-safety с фиксированными правилами теста; канонический контракт вердиктов — arch-02 (200/203/403).

4. UI

Место Поведение
Главная: поле ввода Отправка текста; без auth → согласия → OTP → отложенная отправка
Главная: популярные вопросы Тап = автоотправка текста вопроса (тот же поток, что ручной ввод)
Кнопка «Чат» Открывает текущий активный диалог или создаёт его при отсутствии
Кнопка «Оператор» tel: на operator.call.phone (не чат-сообщение)
Экран чата Лента сообщений клиента и компании; статусы доставки по DTO
Вложения Выбор файла → init → PUT в quarantine → complete → send content_kind=file
Гость в Центре уведомлений / чате Auth-gate; после входа — ЛК

Визуал — по Figma. Figma не канон статусов доставки и состава API.


5. Бизнес-модель

5.1. Сущности

Сущность Схема Назначение
Dialog han_app Диалог клиента с Open Lines
Message han_app Сообщение в диалоге
MessageAttachment han_app Вложение (client upload или company inbound)
safety_tasks han_app Checkpoint sync-wait Message Safety (техническая)
delivery_outbox han_app Durable намерение доставки в Open Lines
openlines_inbox_receipts han_app Idempotency приёма событий local app
dialog_sessions bitrix_local Маппинг чата у bitrix-local-app
popular_questions han_app Справочник текстов быстрых вопросов (не сообщения)

5.2. Dialog.status

Значение Смысл
open Диалог создан, сообщений ещё нет
waiting_for_company Последнее значимое — исходящее от клиента; ждём оператора
waiting_for_client Последнее значимое — входящее от оператора; ждём клиента
closed Закрыт в Open Lines (dialog.closed / ONIMCONNECTORDIALOGFINISH)

Переходы:

Событие Новый статус
POST /dialogs (новый) open
Успешная доставка исходящего клиента в Open Lines waiting_for_company
Сохранено входящее от оператора waiting_for_client
Inbox dialog.closed closed

Из closed обратный переход запрещён. Новый активный диалог после закрытия — снова через POST /dialogs, когда продукт это разрешит; инвариант «один active» сохраняется.

5.3. Сообщение: вид и статусы

content_kind (исходящее MVP):

content_kind Тело запроса Message.text Вложения
text непустой text текст 0
file attachment_id + checksum пустая строка ровно 1

Запрещено: текст + файл → 400 mixed_content_not_allowed; пустое → 400 empty_message; >1 вложение → 400 too_many_attachments.

sender_type: client | company.

safety_status:

Значение Смысл
pending Проверка не завершена
allowed Финальный allow
blocked Финальный deny
needs_review Зарезервирован, в MVP не создаётся

Входящие company всегда safety_status=allowed.

delivery_status:

Значение Смысл Клиенту как финал POST .../messages?
accepted Принято API, safety/доставка ещё не финализированы нет (промежуточный)
processing Sync-wait safety нет
delivered Allow + ушло в Open Lines (или входящее сохранено) да
rejected Deny Message Safety да (422 message_blocked)
failed Инфраструктурная ошибка (Bitrix/S3/timeout), не safety-deny да (503/504)

Инварианты: rejectedblocked; deliveredallowed.

5.4. Вложение

scan_status Смысл
pending В quarantine, проверка не завершена
clean Allow, файл в S3-data (после promote)
infected Deny
failed Ошибка инфраструктуры проверки

direction: client_upload | company_inbound.

Клиентский файл до allow живёт только в S3-quarantine. Постоянных access keys у клиента нет — только короткий presigned PUT/GET.

5.5. Идентификаторы

Имя Назначение
dialog_id UUID диалога = external_chat_id Open Lines
message_id UUID сообщения; ключ idempotent delivery
attachment_id UUID вложения
bitrix_message_id ID сообщения в Bitrix для inbox dedup
task_id Async-проверка Message Safety
Idempotency-Key Клиентский ключ create dialog / send (Redis + durable fallback), TTL 24 ч

5.6. Константы (app_settings)

Ключ Смысл Seed
chat.attachments.allowed_extensions Разрешённые расширения jpg,jpeg,png,webp,heic,heif,pdf
chat.attachments.allowed_mime_types Разрешённые MIME image/jpeg, image/png, …, application/pdf
chat.attachments.disallowed_extensions Явный deny-list svg,doc,docx,xls,xlsx,csv
chat.attachments.max_size_mb Макс. размер 5
chat.attachments.storage Провайдер selectel_s3
chat.attachments.upload_mode Режим presigned_put
chat.attachments.safety_scan_required Safety обязателен true
chat.attachments.presigned_upload_ttl_seconds TTL upload URL 600
rate_limit.message_send.per_user Лимит отправок 30/minute
rate_limit.message_send.per_dialog Лимит на диалог 20/minute
rate_limit.download_url.per_user Лимит download-url 60/hour
operator.call.phone Телефон кнопки «Оператор» +74999591007

Те же chat.attachments.* переиспользуются уведомлениями для документов клиента.

5.7. Популярные вопросы

  • Справочник popular_questions отдаётся public content API.
  • Отдельного backend-flow нет: после auth текст уходит как обычное content_kind=text.
  • Без auth — тот же механизм отложенного сообщения, что у ручного ввода.

6. Поведение UI и сценарии

6.1. Создание / открытие диалога

  1. Frontend перед первым POST .../messages вызывает POST /api/v1/dialogs с Idempotency-Key.
  2. Нет active → 201, status=open.
  3. Есть active → 200, возвращается существующий (новый не создаётся).
  4. Кнопка «Чат» открывает этот диалог (или создаёт при отсутствии).

6.2. Исходящее текстовое сообщение

  1. POST /dialogs (если нет dialog_id).
  2. POST .../messages с content_kind=text, Idempotency-Key.
  3. Rate limits (nginx + app).
  4. Message Safety (текст, ссылки).
  5. Allow → outbox → Open Lines → delivery_status=delivered, Dialogwaiting_for_company.
  6. Deny → 422 message_blocked, в Open Lines не уходит.
  7. Dependency failure → 503/504, при уже созданном Message — delivery_status=failed.

6.3. Исходящий файл

  1. POST .../attachments/init → presigned PUT в S3-quarantine.
  2. Frontend грузит байты напрямую в S3 (не через api-backend).
  3. POST .../attachments/{id}/complete + checksum → HeadObject, metadata, scan_status=pending.
  4. POST .../messages с content_kind=file, attachment_id, checksum.
  5. Safety (файл); при 203 pending api-backend sync-poll task_id внутри того же HTTP-запроса клиента.
  6. Allow → promote quarantine → S3-data attachments → delivery Open Lines (message.files signed URL, message.text пустой).
  7. Deny → quarantine delete, blocked/rejected.

Пока идёт poll safety, это клиентское соединение ждёт; параллельные запросы других клиентов не блокируются.

6.4. Отложенное сообщение (гость → после auth)

Общий frontend-механизм для:

  • ручного ввода;
  • популярного вопроса;
  • CTA уведомлений с send_chat_message (после входа в контуре G).

Порядок:

  1. Клиент инициирует отправку без JWT → согласия → OTP → bootstrapsession-start.
  2. Экран авторизации завершается после успешного bootstrap, даже если последующая отправка в чат упадёт.
  3. Затем POST /dialogsPOST .../messages с сохранённым текстом.
  4. Ошибка Bitrix/safety показывается в контексте чата, не как «не удалось завершить вход» (backlog п.21).

6.5. Входящее от оператора

  1. Bitrix24 ONIMCONNECTOR*bitrix-local-app (inbox, retry, DLQ).
  2. Forward в POST /internal/openlines/v1/inbox (message.new).
  3. api-backend: ownership по external_chat_id, save Message company/allowed/delivered, файлы → S3-data + MessageAttachment.
  4. Текст очищается от служебной разметки отправителя Bitrix (BBCode-префиксы имени и т.п.) — клиент видит чистый текст ответа.
  5. Dialog.statuswaiting_for_client.
  6. Publish WS message.new; при недоступности WS — клиент подтянет через polling.
  7. Local app ack delivery в Bitrix после успешного apply / duplicate-ack.

Лимиты MIME/size для файлов оператора в MVP — те же chat.attachments.*.

6.6. Закрытие диалога

Inbox dialog.closedDialog.status=closed. Frontend получает dialog.status по WS или при следующем GET. Новый активный — только новым POST /dialogs.

6.7. Realtime и fallback

  1. WS /api/v1/realtime + JWT.
  2. После connectedsubscribe с dialog_ids (и опционально notifications).
  3. События чата: message.new, message.status, dialog.status.
  4. Reconnect: backoff 1s…30s; повтор subscribe.
  5. WS недоступен > 30s → polling GET .../messages?after=<cursor> (и notifications counter при подписке).
  6. Ping/pong ~30s.

6.8. Скачивание вложения

GET .../attachments/{id}/download-url → короткий presigned GET + audit attachment.download_url_issued. URL в логи/audit не пишется. Ownership обязателен.


7. Матрицы поведения

7.1. Исходящее: safety → delivery

Вердикт safety safety_status delivery_status (финал клиенту) Open Lines HTTP клиенту
200 allow allowed delivered после успешной отправки; иначе failed да (после allow) 201 или 503/504
403 deny blocked rejected нет 422 message_blocked
203 pending → затем allow/deny как финал как финал только после allow финальный код после poll
timeout / circuit open по политике модуля failed нет 503/504

Клиенту не отдаётся промежуточный processing как успешный ответ POST .../messages.

7.2. Идемпотентность клиента

Ситуация Результат
Тот же Idempotency-Key + то же тело Тот же HTTP-ответ, без повторного side-effect
Тот же ключ + другое тело 409 idempotency_key_reused
Нет ключа на обязательном endpoint 400 validation_error
TTL 24 часа (Redis); durable fallback в idempotency_records

Доставка в Open Lines идемпотентна по message_id. Inbox message.new — unique (external_chat_id, bitrix_message_id).

7.3. Деградация зависимостей

Зависимость Влияние на чат
Redis DB0 Write fail-closed; read ограниченно деградирует
Redis DB1 (realtime) REST работает; WS/publish деградирует → polling
message-safety Отправка недоступна; чтение истории работает
bitrix-local-app / Bitrix После allow сообщение может стать failed; recovery по outbox
S3 Файловые операции недоступны; текстовый чат продолжает работать
Open Lines в readiness Может быть degraded; не обязано валить весь API

7.4. Ownership и ошибки доступа

Ситуация Код
Чужой dialog_id / attachment_id / message 404 (существование не раскрывается)
Нет/невалиден JWT 401
Диалог closed, политика запрещает send по контракту модуля (409/422 — уточнить в OpenAPI при публикации)
Safety deny 422 message_blocked

8. Идентификация и корреляция

  • Публичные id — UUID.
  • dialog_id генерирует приложение при create и передаётся в Open Lines как external_chat_id.
  • При первой доставке bitrix-local-app создаёт/обновляет dialog_sessions.
  • X-Request-ID, traceparent, опционально X-Ux-Session-Id — для логов/audit; не auth.
  • Connector Bitrix: han_mobile_app, Open Line id по env/глоссарию.

9. API

Общие конвенции — arch-02: /api/v1/* JWT, /api/v1/public/*, /internal/{mnemonic}/v1/*; JSON snake_case; даты RFC 3339 UTC; cursors opaque.

9.1. Клиентские (JWT)

Метод и путь Назначение
POST /api/v1/dialogs Find-or-create active dialog (Idempotency-Key)
GET /api/v1/dialogs Список/история диалогов (API есть; UI MVP — один чат)
GET /api/v1/dialogs/{dialog_id} Карточка диалога
GET /api/v1/dialogs/{dialog_id}/messages?after=&limit= История / polling
POST /api/v1/dialogs/{dialog_id}/messages Отправка (Idempotency-Key + safety)
POST .../attachments/init Presigned PUT quarantine
POST .../attachments/{id}/complete Подтверждение upload
GET .../attachments/{id}/download-url Presigned GET + audit
WS /api/v1/realtime События чата (и опционально уведомлений)

9.2. Public

Метод и путь Назначение
GET /api/v1/public/content Тексты + popular_questions
GET /api/v1/public/settings В т.ч. публичные флаги/лимиты UI при is_public

9.3. Internal

Метод и путь Кто → кто Назначение
POST /internal/safety/v1/messages/check api-backend → message-safety Проверка
GET /internal/safety/v1/messages/tasks/{task_id} api-backend → message-safety Poll вердикта
POST /internal/openlines/v1/messages api-backend → bitrix-local-app Исходящая доставка
GET /internal/openlines/v1/dialogs/{external_chat_id} api-backend → bitrix-local-app Reconciliation
POST /internal/openlines/v1/inbox bitrix-local-app → api-backend Входящие события

9.4. Ошибки домена чата

Код HTTP Когда
validation_error 400 Нет Idempotency-Key, невалидное тело
empty_message 400 Нет текста и вложения
mixed_content_not_allowed 400 Текст и файл вместе
too_many_attachments 400 >1 вложение
attachment_not_completed 400 Send до complete
attachment_checksum_mismatch 400 Checksum не совпал
unauthorized 401 JWT
message_blocked 422 Safety deny
idempotency_key_reused 409 Ключ с другим телом
resource_state_conflict 409 Конфликт состояния (напр. complete с другим checksum)
rate_limit_exceeded 429 Лимит
dependency_unavailable 503 Circuit / недоступна зависимость
dependency_timeout 504 Timeout зависимости

9.5. DTO MessageResponse (REST и WS)

{
  "message_id": "uuid",
  "dialog_id": "uuid",
  "sender_type": "client",
  "content_kind": "text",
  "text": "Здравствуйте",
  "attachments": [],
  "safety_status": "allowed",
  "delivery_status": "delivered",
  "created_at": "2026-07-09T12:00:00Z"
}

Сортировка сообщений: created_at ASC (append в ленте). Диалоги: updated_at DESC.


10. Модель данных (схема han_app)

Общие правила — module-01 §9.1 / arch-05.

10.1. dialogs

Поле Описание
id = dialog_id = external_chat_id
user_id FK владельца
status enum §5.2
last_message_at
closed_at при closed
common fields обязательны

Partial unique: один active dialog на user_id.

10.2. messages

Поле Описание
id message_id
dialog_id FK
sender_type client | company
content_kind text | file
text непустой для text; '' для file
safety_status / delivery_status §5.3
external_message_id Bitrix id для inbound
client_idempotency_key опционально/связка с Idempotency-Key
occurred_at
common fields

Индексы: лента (dialog_id, created_at, id); recovery по delivery_status; unique inbound (dialog_id, external_message_id).

Решение: исходящее создаётся до завершения safety со статусами pending/accepted, чтобы safety_tasks имел FK (crash checkpoint).

10.3. message_attachments

Поля: id, dialog_id, message_id NULL до привязки, owner_user_id, direction, имена/MIME/size/checksum, scan_status, storage keys (working + quarantine), timestamps, common fields.

MVP: unique partial — не более одного active attachment на message_id.

10.4. Технические таблицы

Таблица Назначение Физическая очистка
safety_tasks Poll/recovery Message Safety да, по retention
delivery_outbox App → Open Lines по статусам/retention модуля
openlines_inbox_receipts Dedup inbox по политике модуля
idempotency_records Durable fallback Idempotency-Key по expires_at

10.5. Схема ключей S3 (чат)

quarantine/...          # клиентский upload до вердикта
attachments/...         # проверенные вложения чата (client + company)

Бакет documents (han-chat-documents) — для документов профиля/уведомлений, не hot path чата Open Lines.


11. Фоновые процессы

Процесс Владелец Назначение
Safety recovery worker api-backend Доводит 203 pending после обрыва клиентского HTTP
Delivery outbox worker api-backend Retry доставки в local app / Open Lines
Quarantine orphan cleanup api-backend / ops Удаляет просроченные объекты без active task/attachment
Inbox retry / DLQ bitrix-local-app Надёжность webhook → API
Realtime publish api-backend + Redis DB1 Fan-out WS; при сбое — polling

Application-код не обходит outbox «в обход» для повторной доставки без idempotency.


12. Audit и observability

event_type Когда
dialog.created Новый диалог
message.submitted Принято к обработке
message.blocked Safety deny
message.delivered Успех Open Lines / inbound saved
message.failed Инфраструктурный fail
attachment.upload_initialized / completed / promoted / rejected Lifecycle файла
attachment.download_url_issued Presigned GET
openlines.inbox_applied Входящее применено

Метрики: latency send, safety poll duration/timeout, outbox depth/age/DLQ, inbox duplicate/apply, WS reconnect/drop, rate limit rejects. Нельзя использовать user_id/dialog_id как metric labels.

В логах нет: тел сообщений, tokens, presigned URL, Bitrix download URL, полного PII.


13. Хранение

  • Dialog / Message / MessageAttachment — прикладные строки, soft-delete, физическое удаление запрещено.
  • Закрытые диалоги и их сообщения остаются в БД (история API); UI MVP показывает текущий чат.
  • Quarantine и технические checkpoint — исключения с retention.
  • S3-data attachments живут с записью вложения; lifecycle бакетов — ops-политика.

14. Смежные сервисы

Компонент Роль в чате
Frontend UI чата, отложенное сообщение, upload, WS/polling, кнопки Чат/Оператор
api-backend Dialogs/messages/attachments, safety orchestration, outbox, inbox apply, realtime
message-safety Вердикт allow/deny/pending по исходящим
bitrix-local-app Connector Open Lines, исходящие/входящие, dialog_sessions
Bitrix24 Open Lines Рабочее место оператора
S3 Quarantine + attachments
Redis Rate limit, idempotency cache, realtime coordination
nginx /api/* + WS upgrade; proxy_read_timeout ≥ safety poll budget + запас
bitrix-sync Не в hot path чата

Порядок работ (если поднимать домен с нуля): (1) dialogs + messages text path + idempotency → (2) safety orchestration → (3) Open Lines out + inbox → (4) attachments → (5) WS + polling → (6) recovery/outbox hardening.


15. Влияние на arch-документы

Документ Содержание по чату
arch-00-glossary.md Dialog, Message, статусы, Open Lines terms
arch-01-system-architecture.md Потоки C→O и O→C, создание диалога
arch-02-api-contracts.md REST/WS/safety/openlines контракты
arch-03-docker-compose-blueprint.md Сервисы, WS location, timeouts
arch-04-settings-and-content.md chat.attachments.*, rate limits, operator phone
module-01-api-backend.md Алгоритмы, таблицы, workers
module-05-message-safety.md Stub/production safety
module-06-bitrix-local-app.md Connector / inbox / out

Этот документ собирает бизнес-смысл обмена сообщениями для аналитики и смежных фич; детальные алгоритмы — в module-спеках.


16. Критерии приёмки

  1. Без JWT отправить сообщение нельзя; после auth отложенный текст/популярный вопрос уходит штатным POST /dialogsPOST .../messages.
  2. Не более одного active dialog; повторный POST /dialogs возвращает существующий.
  3. Text и file взаимоисключающи; mixed/empty/too many → соответствующие 400.
  4. POST .../messages возвращает только финальный статус; deny → 422 message_blocked без доставки в Bitrix.
  5. Allow → сообщение видно оператору в Open Lines; Dialogwaiting_for_company.
  6. Ответ оператора появляется в клиенте через WS или polling; Dialogwaiting_for_client; текст без служебной разметки имени из Bitrix.
  7. dialog.closed закрывает диалог; из closed нельзя вернуться тем же id.
  8. Файлы: quarantine → safety → promote; deny чистит quarantine; download только presigned + audit.
  9. Идемпотентность create/send соблюдается 24 ч; повтор delivery/inbox не плодит дубли.
  10. При падении WS > 30s клиент уходит в polling и не теряет сообщения, уже лежащие в App DB.
  11. Недоступность safety блокирует send, но не чтение истории; недоступность S3 не ломает text-only чат.
  12. Ошибка отправки после успешного bootstrap не выглядит как ошибка входа.
  13. Кнопка «Оператор» берёт номер только из operator.call.phone.
  14. Популярный вопрос не имеет отдельного API — только text message.
  15. Ownership: чужие dialog/attachment → 404.

17. Журнал решений и открытых вопросов (ненормативно)

17.1. Принятые решения

# Решение Раздел / источник
D1 Чат ≠ уведомления; непрочитанный чат не есть Notification §1, notification-requirements
D2 Один active dialog; dialog_id = external_chat_id §2, arch-01
D3 MVP content: text XOR file §5.3, arch-02
D4 Клиент ждёт финальный вердикт на одном HTTP; poll safety внутри api-backend §6.2–§6.3
D5 Исходящие проходят Message Safety; входящие оператора — доверенный канал §2, arch-02
D6 Durable outbox + idempotent delivery; inbox dedup §6, module-01
D7 WS обязателен как primary realtime; polling — fallback §6.7
D8 UI истории диалогов deprecated; API списка сохраняется §3.2
D9 Отложенное сообщение — общий frontend-механизм §6.4
D10 Популярный вопрос = обычный text send §5.7
D11 Presigned upload напрямую в S3; api-backend не проксирует байты §6.3
D12 chat.attachments.* — единые лимиты для чата (и reuse уведомлениями) §5.6

17.2. Открытые вопросы

# Вопрос Предложение Влияние
Q1 Индикатор непрочитанных сообщений чата + sync между устройствами (backlog 2324) Поле вроде Dialog.client_last_opened_at / last_read_message_id + WS/REST counter; не тип уведомления message Новая мини-постановка
Q2 Точный HTTP-код send в уже closed dialog Зафиксировать в OpenAPI (409 resource_state_conflict или 422) Клиентский UX
Q3 Политика показа blocked-сообщения в ленте (полный текст vs redacted) Минимизация PII в хранении blocked (M8 module-01) + нейтральный UI DB + frontend
Q4 Создание нового dialog сразу после closed — всегда разрешено или по бизнес-правилу «сессия поддержки» MVP: разрешить, пока соблюдён unique active Продукт / поддержка
Q5 Stub message-safety с 400 на GET task vs канон arch-02 403 Для prod — только канон 200/203/403; stub не расширяет публичный контракт module-05 / contract tests
Q6 Смешанный content text+files Отдельный API version post-MVP arch-02 breaking
Q7 Unread badge на кнопке «Чат» vs бейдж колокольчика уведомлений Развести визуально и в данных UI + Q1

18. Связь со смежными доменами

Домен Связь
Пользователь Чат только после JWT + bootstrap; отложенное сообщение после входа
Уведомления CTA send_chat_message пишет в чат; кнопки Чат/Оператор на главной; тип message в уведомлениях запрещён
Документы профиля Другой бакет/реестр; не заменяют вложения чата
CRM (bitrix-sync) Contact map по телефону параллельно; не доставляет сообщения Open Lines

Владелец ленты и статусов доставки — api-backend + App DB; рабочее место оператора — Bitrix24 Open Lines через bitrix-local-app.