Event Contract: pin.support.ticket-message-posted (TicketMessagePostedKafkaEvent)
Версионируемый контракт интеграционного события «в тикет добавлен публичный ответ Службы заботы».
Consumer-документы ссылаются сюда, не переописывая payload. Событие фиксирует факт добавления публичного
сообщения оператором (ticket-message.md) в тикет
(ticket.md) через операцию reply-ticket и несёт минимальный snapshot для
уведомления заявителя о новом ответе. Единственный потребитель — notifications (уведомить
заявителя-адресата о поступившем ответе поддержки).
Назначение
Даёт notifications факт нового публичного ответа поддержки, чтобы уведомить заявителя («по вашему
обращению SUP-NNN получен ответ») без синхронного запроса в support. Публикуется только для reply-ticket
с Visibility = Public (ответ оператора Службы заботы, матрица 4.1/4.5): внутренние заметки (Internal) и
сообщения самого заявителя события не порождают — заявителю внутренние заметки не видны, а о собственном
сообщении его не уведомляют. ФИО/телефон/email участников в payload не входят — потребитель берёт их из
собственной реплики identity (pin.identity.users.v1); событие несёт только RequesterUserId
(адресат) и AuthorUserId (ответивший оператор). Тело ответа в событие не включается: текст читается по API
тикета/треда (антитеза PII в шине). Публичное в границах платформы (m2m ACL Kafka), т.к. служит внутренней
доставке уведомлений.
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.support.ticket-message-posted |
| Topic | pin.support.ticket-message-posted.v1 (выделенный топик) |
| Partitions / key | 3 (низкий объём, ADR-0056) / ключ = TicketId (= AggregateId; per-ticket порядок сообщений) |
| ContractVersion | 1 |
| Producer | support: reply-ticket (Visibility = Public) — ../api/support/reply-ticket.md |
| Статус | draft |
Событие публикуется атомарно с добавлением публичного сообщения в тикет: пока изменение не зафиксировано, событие не отправляется потребителям; после фиксации — доставляется гарантированно (at-least-once). Метаданные события:
MessageId(Guid v7, для дедупликации),AggregateId = TicketId(= Kafka key, per-ticket порядок),AggregateType = "Ticket".
Payload TicketMessagePostedKafkaEvent
Дом полей — entity: TicketMessageEntity (раздел «Доменные и интеграционные
события») и родитель TicketEntity; здесь перечислено только то, что реально в событии.
Все поля — скаляры/enum-строки; вложенных моделей нет.
| Поле | Тип | Обязательность | Источник | Compatibility | Описание |
|---|---|---|---|---|---|
TicketId | Guid | Да | TicketMessageEntity.TicketId (= Ticket.Id) | Immutable | Ключ агрегата (= AggregateId, Kafka key). |
Number | long | Да | Ticket.Number | Immutable | Человекочитаемый номер (SUP-NNN) для текста уведомления/диплинка. |
TenantId | Guid | Да | Ticket.TenantId | Immutable | PIN CompanyId (единый tenant платформы, ADR-0052). |
MessageId | Guid | Да | TicketMessageEntity.Id | Immutable | Идентификатор добавленного сообщения (для диплинка на тред). |
RequesterUserId | Guid | Да | Ticket.RequesterUserId | Immutable | Заявитель — адресат уведомления (id, не PII); ФИО/телефон — из реплики identity у потребителя. |
AuthorUserId | Guid | Да | TicketMessageEntity.AuthorUserId | Immutable | Оператор Службы заботы, добавивший ответ (id, не PII). |
AuthorKind | string (enum TicketMessageAuthorKind) | Да | TicketMessageEntity.AuthorKind | Additive only (новые значения) | Роль автора; для этого события всегда Operator (супервизор отвечает как оператор; Requester/System события не порождают). Включён для единообразия. |
Visibility | string (enum TicketMessageVisibility) | Да | TicketMessageEntity.Visibility | Additive only | Всегда Public в момент публикации (INV-MSG-2; Internal в шину не уходит); включён для единообразия. |
HasAttachments | bool | Да | TicketMessageEntity.Attachments?.Items.Any() | Additive only | Есть ли вложения (для формулировки «ответ с вложением»); сами метаданные вложений в событие не входят. |
PostedAt | DateTimeOffset | Да | TicketMessageEntity.CreatedAt | Immutable | Момент добавления ответа. |
Enum-значения не дублируются — дом ticket-message.md → «Enum TicketMessageAuthorKind/TicketMessageVisibility». Тело сообщения (Body), рендер шаблона, TemplateId, метаданные вложений и ФИО/телефон/email участников в payload не входят — потребителю (notifications) для формулировки достаточно Number + признака вложений; текст читается по API тикета, display-подпись — из реплики identity.
Когда публикуется
| Условие | Действие | Публикация |
|---|---|---|
reply-ticket с Visibility = Public (оператор отвечает заявителю) | добавление публичного сообщения (INV-MSG-2/5; при первом публичном ответе — FirstResponseAt, INV-TKT-5) | атомарно со сменой состояния агрегата |
Событие не публикуется: для reply-ticket с Visibility = Internal (внутренняя заметка, заявителю не
видна) и для сообщений заявителя (add-ticket-message, AuthorKind = Requester — о собственном ответе не
уведомляют; возврат тикета в работу несёт ticket-status-changed.md при
авто-реопене). Смена статуса тикета публикуется отдельным событием
ticket-status-changed.md; создание тикета — ticket-created.md.
Потребители (consumers)
| Consumer-контекст | Side effects | Идемпотентность | Задержка (SLA) |
|---|---|---|---|
notifications | Уведомление заявителю (RequesterUserId) о новом публичном ответе по обращению Number; каналы получателя — своя реплика identity | first-seen по MessageId + per-ticket порядок по ключу TicketId | < 1 мин |
Порядок сообщений одного тикета гарантирован общим ключом TicketId (одна партиция на ключ); отображаемое имя
оператора/заявителя консюмер берёт из собственной реплики identity, не из этого события.
Доставка и восстановление. At-least-once; дедупликация по MessageId (first-seen). Транзиентный сбой —
ретраи, исчерпание → DLQ с алертом (без блокировки партиции). Порядок гарантирован внутри этого топика;
межтопикового порядка с ticket-status-changed (авто-реопен при ответе заявителя) и ticket-created нет —
уведомление о новом ответе самодостаточно по Number/MessageId/HasAttachments, см.
README.md («Гарантии доставки, порядка и восстановления»).
Версионирование и совместимость
ContractVersion = 1; приVersion != ContractVersionconsumer логирует warning и продолжает обработку. Топик кодирует версию суффиксом.v1по конвенции.- Additive-only для minor: новые optional-поля с безопасным default — без bump. Новые значения enum
TicketMessageAuthorKind/TicketMessageVisibility— additive. - Breaking (удаление/переименование/смена типа/смысла) —
ContractVersion = 2+ новый топикpin.support.ticket-message-posted.v2+ dual-publish на период миграции + deadline миграции consumers + sunset.v1. - Unknown enum (
AuthorKind/Visibility) — fallback: сохранить строку как есть, warning, не падать (уведомление с нейтральной формулировкой).
Безопасность
Событие несёт только идентификаторы, enum-значения, признак вложений и метку времени — прямого PII нет по
построению: ФИО/телефон/email участников и тело ответа намеренно исключены (потребитель резолвит подпись из
своей реплики identity, текст — по API тикета).
| Поле | Класс данных | Правило |
|---|---|---|
TicketId, Number, TenantId, MessageId, RequesterUserId, AuthorUserId | Internal | include (id/номер, не PII) |
AuthorKind, Visibility, HasAttachments | Internal | include (enum-строки / bool) |
PostedAt | Internal | include |
Событие НЕ содержит secrets, tokens, raw credentials, ФИО/телефон/email, тела сообщения/рендера шаблона,
вложений. Доступ к топику — m2m ACL Kafka (notifications).
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity: TicketMessageEntity (child of Ticket)
- API (1): POST /api/support/tickets/{id}/messages — Ответ оператора / внутренняя заметка
- Use Cases (1): UC-SUP-002. Решение обращения
- События (2): События сервиса support: карта топиков и решения об интеграциях, Event Contract: pin.support.ticket-created (TicketCreatedKafkaEvent)
- Консюмеры (10): Consumer: контроль смены телефона связанного инвестора (pin-investor-owner-identity-users), Consumer: Calendar Identity User Sync (calendar-identity-users), Consumer: CRM Identity User Sync (CrmUserUpsertedConsumer), Consumer: Identity Events → Notifications (notifications-identity-events), Consumer: identity-users-sync (ProfileUserUpsertedConsumer, ProfileUserBlockedConsumer), Consumer: identity.users.v1 → AuthorReplica (news-identity-users), Consumer: Messenger User Replica Sync (MessengerUserUpsertedConsumer), Consumer: ReferralUserUpsertedConsumer (pin.identity.users.v1), Consumer: Support Identity User Sync, Consumer: Wallet Identity Display Sync (wallet-identity-users)
- registries (1): Реестр событий PIN
- index.md (1): Support Service
Связанные документы
- Источник (операция): ../api/support/reply-ticket.md (шаг 6 — publish
Public); сценарий — ../use-cases/UC-SUP-002-resolve-ticket.md;
сущности — ticket-message.md (INV-MSG-2/5), ticket.md
(INV-TKT-5,
Ticket.AddMessage). - Соседние контракты топиков
support: ticket-created.md, ticket-status-changed.md, arbitrage-resolved.md; карта — README.md.