Event Contract: pin.calendar.appointment-confirmed (AppointmentConfirmedEvent)
Версионируемый контракт интеграционного события «Запись подтверждена» — встреча инвестора и агента
состоится. Consumer-документы ссылаются сюда, не переописывая payload. Дом полей агрегата —
entity: AppointmentEntity.
Назначение
Фиксирует подтверждение записи: агент принял Pending-запись (confirm-appointment) либо запись создана
сразу подтверждённой (слот с AutoConfirm = true). Публичное: crm пополняет ленту лида фактом
подтверждённой встречи, notifications уведомляет инвестора «встреча подтверждена» — без синхронного
запроса в calendar.
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.calendar.appointment-confirmed |
| Topic | pin.calendar.appointment-confirmed.v1 |
| Partitions / key | 3 (ADR-0056) / key = AppointmentId (порядок событий одной записи) |
| ContractVersion | 1 |
| Producer | calendar: подтверждение агентом (../api/agent/confirm-appointment.md, permission calendar.appointments.decide, до PendingExpiresAt); auto-confirm при бронировании (../api/public/book-slot.md, слот с AutoConfirm = true) |
| Статус | draft |
Событие публикуется атомарно с переходом записи → Confirmed (для auto-confirm — атомарно со всей
связкой создания, вместе с appointment-created): пока изменение не зафиксировано,
событие не отправляется; после фиксации — доставляется гарантированно (at-least-once, transactional
outbox), ключ = AppointmentId.
Payload AppointmentConfirmedEvent
Дом полей — entity: AppointmentEntity (раздел «Доменные и интеграционные
события»); здесь перечислено только то, что реально в событии. Контракт самодостаточен: несёт участников и
время встречи — консюмеру не нужно состояние из ранее обработанного appointment-created.
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
AppointmentId | Guid | Да | AppointmentEntity.Id | Immutable | Ключ агрегата (= Kafka key). |
TenantId | Guid | Да | AppointmentEntity.TenantId | Immutable | Компания агента (ADR-0052). |
AgentUserId | Guid | Да | AppointmentEntity.AgentUserId | Immutable | Агент/консультант. |
InvestorUserId | Guid | Да | AppointmentEntity.InvestorUserId | Immutable | Инвестор — получатель уведомления о подтверждении. |
Kind | string (enum EventKind) | Да | AppointmentEntity.Kind | Additive only (новые значения) | Вид встречи: Showing / Consultation. |
StartAt | DateTimeOffset | Да | AppointmentEntity.StartAt | Immutable (снапшот слота) | Начало встречи, UTC. |
TimeZone | string | Да | AppointmentEntity.TimeZone | Additive only | IANA-таймзона слота — для отображения локального времени в уведомлении. |
ConfirmedAt | DateTimeOffset | Да | AppointmentEntity.ConfirmedAt | Immutable | Момент подтверждения (для auto-confirm совпадает с созданием записи). |
AutoConfirmed | bool | Да | производное: подтверждение выполнено при бронировании (AutoConfirm слота), а не решением агента | Immutable | true — auto-confirm; false — агент подтвердил вручную. |
Когда публикуется
| Условие | Переход состояния | Публикация |
|---|---|---|
Агент подтвердил Pending-запись до PendingExpiresAt | Pending → Confirmed | атомарно с переходом записи (AutoConfirmed = false) |
Бронирование слота с AutoConfirm = true | [*] → Confirmed | атомарно со всей связкой создания; параллельно публикуется appointment-created (AutoConfirmed = true) |
Слот и календарное событие при подтверждении не меняются (остаются Booked / Planned — таблица каскадов
в appointment.md).
Потребители (consumers)
| Consumer-контекст | Side effects | Идемпотентность | Задержка (SLA) |
|---|---|---|---|
crm | Запись в ленту лида «встреча подтверждена» (вид, время, AutoConfirmed); get-or-create клиента/лида — как в appointment-created (контракт самодостаточен на случай обработки раньше appointment-created). Консюмер — ../../crm/consumers/calendar-appointments.md | MessageId (unique-ключ записи ленты) + business key AppointmentId | < 1 мин |
notifications | NotificationKind = AppointmentConfirmed: инвестору — «встреча подтверждена» с локальным временем (StartAt + TimeZone); агенту уведомление не строится (ручное подтверждение — его собственное действие, при auto-confirm агента уведомил appointment-created). Консюмер — ../../notifications/consumers/calendar-events.md | MessageId | < 5 мин |
Версионирование и совместимость
ContractVersion = 1; при несовпадении версии контракта потребитель логирует предупреждение и продолжает обработку. Топик кодирует версию по конвенции суффиксом.v1.- Additive-only для minor: новые поля добавляются как optional с безопасным default — без bump.
- Breaking (удаление/переименование/смена типа или смысла) —
ContractVersion = 2+ новый топикpin.calendar.appointment-confirmed.v2+ dual-publish на период миграции + deadline миграции consumers + sunset.v1. - Unknown enum (
Kind) — fallback: сохранить строку как есть, warning; уведомление с нейтральным текстом «встреча подтверждена» (fail safe).
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
AppointmentId, TenantId, AgentUserId, InvestorUserId, Kind, StartAt, TimeZone, ConfirmedAt, AutoConfirmed | Internal | include (только идентификаторы, время и флаг) |
- PII (ФИО/телефоны) в событии отсутствует по дизайну: display-данные консюмеры берут из своей реплики
pin.identity.users.v1; свободнотекстовых полей в контракте нет. - Доступ к топику — m2m ACL Kafka (
crm,notifications).
Доставка, retry/DLQ и восстановление
- Гарантия. At-least-once после атомарной публикации из outbox; ключ =
AppointmentId— порядок событий одной записи в топике сохранён. Дубликаты возможны — консюмеры идемпотентны поMessageId. - Retry. Транзиентный сбой консюмера → повтор с backoff; партиция не продвигается дальше застрявшего оффсета, порядок по ключу сохраняется.
- DLQ. Неразбираемое/несовместимое сообщение после конечного числа попыток — в DLQ с алертом; записи других инвесторов не блокируются.
- Кросс-топик порядок. С appointment-created (auto-confirm) и appointment-cancelled межтопиковый порядок не гарантирован: контракт самодостаточен, а устаревшее подтверждение поверх уже обработанной отмены/исхода консюмер применяет по бизнес-состоянию (no-op, warning) — финальную картину дают appointment-cancelled / appointment-completed.
- Промежуточное состояние. Подтверждение в календаре агента и в «моих записях» инвестора видно сразу; уведомление и лента CRM — после обработки событий (обычно < 1–5 мин).
Acceptance-критерии (контракт потребления)
- Ручное подтверждение. Дано:
Pending-запись подтверждена агентом. Когдаnotificationsобрабатывает событие. Тогда инвестору уходитAppointmentConfirmedс локальным временем встречи;AutoConfirmed = false. - Auto-confirm. Дано: слот с
AutoConfirm = trueзабронирован. Когда опубликованы оба события создания и подтверждения. ТогдаAutoConfirmed = true,ConfirmedAtсовпадает с созданием записи; уведомление агенту строится только поappointment-created(дублей нет). - crm — лента. Дано: событие обработано
crm. Тогда в ленте лида — запись «встреча подтверждена»; повторная доставка дубля не создаёт. - Порядок топиков. Дано:
appointment-confirmedобработан раньшеappointment-created(auto-confirm). Тогда get-or-create клиента/лида выполняется по данным этого события, обработкаappointment-createdследом дополняет ленту без дублей. - Несовместимый payload. Дано: сообщение без обязательного поля. Когда консюмер повторно не смог его обработать. Тогда сообщение уходит в DLQ, остальные записи обрабатываются.
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (1): US-реестр контекста calendar (US-CAL)
- События (3): Event Contract: pin.calendar.appointment-cancelled (AppointmentCancelledEvent), Event Contract: pin.calendar.appointment-completed (AppointmentCompletedEvent), Event Contract: pin.calendar.appointment-created (AppointmentCreatedEvent)
- Консюмеры (2): Consumer: Calendar Events → Notifications (notifications-calendar-events), Consumer: CRM Calendar Appointments Sync (crm-calendar-appointments)
- scenarios (1): UC-OVR-011. Самозапись инвестора: от слота до исхода встречи и лида в воронке
- registries (1): Реестр событий PIN
Связанные документы
- Сущность/дом полей: appointment.md (переход
Pending → Confirmed,PendingExpiresAt, каскады). - Соседние контракты семейства: appointment-created.md, appointment-cancelled.md, appointment-completed.md.
- Producer-операции:
../api/agent/confirm-appointment.md,../api/public/book-slot.md; consumers:../../crm/consumers/calendar-appointments.md,../../notifications/consumers/calendar-events.md. - ADR: 0064 calendar-контекст, 0052 tenancy, 0056 simplicity-first.