Event Contract: pin.user-activity.session-linked (SessionLinkedKafkaEvent)
Версионируемый контракт интеграционного события «сессия связана с пользователем». Consumer-документы ссылаются сюда, не переописывая payload.
Назначение
Фиксирует момент, когда гостевая сессия устройства связывается с учётной записью: Session.UserId
переходит null → value при входе (INV-SES-002 «LinkOnce»). Публичное — даёт downstream-контекстам
snapshot «этот AnonymousId теперь принадлежит этому UserId» без синхронного запроса в user-activity:
CRM привязывает гостевую активность к лиду инвестора, favorites получает кандидата на merge гостевого
избранного. Дом изменения — entity: Session (раздел «Доменные и интеграционные
события», строка SessionLinkedKafkaEvent). Обязательных потребителей нет — событие включено на будущее
(ADR-0054); триггер фактического merge избранного выбран фронтовый (README favorites).
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.user-activity.session-linked |
| Topic | pin.user-activity.session-linked.v1 |
| Partitions / key | 6 (ADR-0056 scale envelope) / key = AggregateId = SessionId (порядок per-session) |
| ContractVersion | 1 |
| Producer | user-activity: линковка (POST /sessions/link, JWT claim sub) и UserActivitySignedInConsumer (событие pin.identity.user-signed-in) |
| Статус | draft |
Событие публикуется атомарно с мутацией агрегата. Метаданные события несут MessageId, CreatedAt,
Version (= ContractVersion), AggregateId = SessionId, AggregateType = "Session".
Payload SessionLinkedKafkaEvent
Дом полей — entity: Session; здесь перечислено только то, что реально в событии.
Все поля скалярные — вложенных моделей нет. Payload не содержит Device.* (IpAddress/UserAgent — вне
событий, правило session.md).
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
SessionId | Guid | Да | Session.Id | Immutable | Ключ агрегата (= Kafka key = AggregateId). Корень гостевой/пользовательской активности устройства. |
AnonymousId | Guid | Да | Session.AnonymousId | Immutable | Устройство/браузер, чья активность привязывается. Тот же id владеет гостевыми записями favorites (контракт X-Anonymous-Id). |
UserId | Guid | Да | Session.UserId | Immutable | Пользователь, с которым связана сессия. В этом событии всегда заполнен (событие фиксирует переход null → value). Логическая ссылка identity.users(id). |
LinkedAt | DateTimeOffset | Да | Session.LinkedAt | Immutable | Момент связывания (server clock, now()). Заполнен ⇔ UserId заполнен (chk_sessions_link_pair). |
Когда публикуется
| Условие | Переход состояния | Согласованность |
|---|---|---|
Вход на устройстве с гостевой Active-сессией: линковка (POST /sessions/link по JWT sub) либо UserActivitySignedInConsumer (событие pin.identity.user-signed-in) устанавливает UserId и LinkedAt (INV-SES-002) | Session.UserId: null → value (LinkedAt = now()); Status остаётся Active | мутация агрегата + событие в одном согласованном изменении |
Публикуется один раз на сессию (LinkOnce). Повторный вход того же пользователя — no-op (события нет).
Вход другого пользователя на устройстве с уже связанной Active-сессией (INV-SES-002) не даёт
session-linked: текущая сессия форс-закрывается (session-ended), а новая сессия
рождается уже связанной (UserId установлен при создании — SessionStartedEvent, не переход null → value).
Потребители (consumers)
Обязательных consumer-ов нет (event включён на будущее, ADR-0054); ниже — кандидаты, сверено с README и session.md.
| Consumer-контекст | Consumer | Side effects | Идемпотентность | Задержка (SLA) |
|---|---|---|---|---|
crm (кандидат) | CrmSessionLinkedConsumer | Привязка гостевой активности (AnonymousId) к лиду инвестора (UserId) — гостевые просмотры/действия «до входа» видны в карточке лида | MessageId + business key SessionId | < 5 мин |
favorites (кандидат) | FavoritesSessionLinkedConsumer | Кандидат на merge гостевого избранного (AnonymousId → UserId); фактический триггер merge — фронтовый (README favorites), событие — резервный/аудит-путь | MessageId (no-op при повторе) | < 5 мин |
Ретроактивная атрибуция (окно согласованности). Гостевые action-tracked/property-object-view-ended
этого устройства (ключ/поле AnonymousId) могли прийти в crm/dev-stats до session-linked —
топики независимы. Потребитель-кандидат crm привязывает уже полученную гостевую активность к лиду
ретроактивно по AnonymousId → UserId из этого события (в пределах SLA < 5 мин); до обработки
session-linked действия «до входа» в карточке лида ещё не сгруппированы под инвестором. Обработка
идемпотентна по SessionId/MessageId — повторная доставка не меняет привязку.
Версионирование и совместимость
ContractVersion = 1; при несовпадении версии consumer логирует warning и продолжает (upcasting/schema-migration нет). Топик кодирует версию суффиксом.v1по конвенции.- Additive-only для minor: новые optional-поля с безопасным default — без bump.
- Breaking (удаление/переименование/смена типа/смысла) — новый
ContractVersion = 2+ топикpin.user-activity.session-linked.v2+ dual-publish на период миграции + deadline миграции consumers + sunset.v1. - Unknown enum — неприменимо (в payload enum-полей нет).
- Порядок per-session гарантирован ключом
SessionId; событие публикуется единожды на сессию, поэтому межсобытийного упорядочивания consumer-у держать не требуется (кроме дедупа поMessageId).
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
SessionId, AnonymousId, UserId | Internal | include |
LinkedAt | Internal | include |
PII в payload нет — только идентификаторы и timestamp. Device.IpAddress (квази-PII), UserAgent и любые
тексты пользователя в событие не включаются. Доступ к топику — m2m ACL Kafka (кандидаты crm/favorites).
Событие НЕ содержит secrets, токенов, credentials, платёжных данных, unbounded-вложений.
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (2): UC-ACT-001. Трекинг сессии гостя и пользователя, UC-ACT-005. Слияние гостевой и пользовательской активности при входе
- События (1): Event Contract: pin.user-activity.session-ended (SessionEndedKafkaEvent)
- Консюмеры (1): Consumer: Identity Sign-Ins (UserActivitySignedInConsumer)
- registries (1): Реестр событий PIN
Связанные документы
- Сущность (дом изменения): session.md (INV-SES-002 LinkOnce,
chk_sessions_link_pair); соседние контракты топиков контекста: session-ended.md, property-object-view-ended.md, action-tracked.md. - Источники (producer): публичный
link(../api/), консюмер входа ../consumers/identity-signed-in.md; upstream-событие входа ../../identity/events/user-signed-in.md. - Потребители: ../../crm/README.md (карточка лида),
../../favorites/README.md(X-Anonymous-Id, merge). - Обзор контекста: ../README.md.
- Решения: ADR-0052 (вне tenant), ADR-0054 (объединение
user-activity, событие на будущее), ADR-0056 (simplicity-first, scale envelope).