Перейти к основному содержимому

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 typepin.support.ticket-message-posted
Topicpin.support.ticket-message-posted.v1 (выделенный топик)
Partitions / key3 (низкий объём, ADR-0056) / ключ = TicketId (= AggregateId; per-ticket порядок сообщений)
ContractVersion1
Producersupport: 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Описание
TicketIdGuidДаTicketMessageEntity.TicketId (= Ticket.Id)ImmutableКлюч агрегата (= AggregateId, Kafka key).
NumberlongДаTicket.NumberImmutableЧеловекочитаемый номер (SUP-NNN) для текста уведомления/диплинка.
TenantIdGuidДаTicket.TenantIdImmutablePIN CompanyId (единый tenant платформы, ADR-0052).
MessageIdGuidДаTicketMessageEntity.IdImmutableИдентификатор добавленного сообщения (для диплинка на тред).
RequesterUserIdGuidДаTicket.RequesterUserIdImmutableЗаявитель — адресат уведомления (id, не PII); ФИО/телефон — из реплики identity у потребителя.
AuthorUserIdGuidДаTicketMessageEntity.AuthorUserIdImmutableОператор Службы заботы, добавивший ответ (id, не PII).
AuthorKindstring (enum TicketMessageAuthorKind)ДаTicketMessageEntity.AuthorKindAdditive only (новые значения)Роль автора; для этого события всегда Operator (супервизор отвечает как оператор; Requester/System события не порождают). Включён для единообразия.
Visibilitystring (enum TicketMessageVisibility)ДаTicketMessageEntity.VisibilityAdditive onlyВсегда Public в момент публикации (INV-MSG-2; Internal в шину не уходит); включён для единообразия.
HasAttachmentsboolДаTicketMessageEntity.Attachments?.Items.Any()Additive onlyЕсть ли вложения (для формулировки «ответ с вложением»); сами метаданные вложений в событие не входят.
PostedAtDateTimeOffsetДаTicketMessageEntity.CreatedAtImmutableМомент добавления ответа.

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; каналы получателя — своя реплика identityfirst-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 != ContractVersion consumer логирует 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, AuthorUserIdInternalinclude (id/номер, не PII)
AuthorKind, Visibility, HasAttachmentsInternalinclude (enum-строки / bool)
PostedAtInternalinclude

Событие НЕ содержит secrets, tokens, raw credentials, ФИО/телефон/email, тела сообщения/рендера шаблона, вложений. Доступ к топику — m2m ACL Kafka (notifications).

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы