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

Event Contract: pin.user-activity.session-ended (SessionEndedKafkaEvent)

Версионируемый контракт интеграционного события «Сессия завершена». Consumer-документы ссылаются сюда, не переописывая payload. Событие — терминальное: ровно одно на сессию (INV-SES-006 InactiveImmutable). Дом полей — entity: Session; здесь перечислено только то, что реально в payload.

Назначение

Фиксирует переход сессии Active → Inactive (закрытие). Публикуется в двух случаях: worker activity-closer закрыл сессию по неактивности (порог 10–15 мин, конфиг) или связывание форс-закрыло её при входе другого пользователя на устройстве (INV-SES-002). В момент закрытия сервер считает Duration = LastActivityAt − CreatedAt (ADR-0056 п.3). Публичное: даёт downstream завершённую сессию с готовой длительностью без синхронного запроса в user-activity — единственный обязательный consumer dev-stats строит сессионные метрики (ADR-0057).

Metadata

ПолеЗначение
Event typepin.user-activity.session-ended
Topicpin.user-activity.session-ended.v1 (отдельный топик; session-linked — свой топик)
Partitions / key6 (1–10k активных сессий, ADR-0056) / key = SessionId (AggregateId)
ContractVersion1
Produceruser-activity: worker activity-closer (закрытие по неактивности), связывание (форс-закрытие при входе другого пользователя, INV-SES-002)
Статусdraft

Событие публикуется атомарно с мутацией агрегата. Метаданные события несут MessageId, CreatedAt, Version (= ContractVersion), AggregateId = SessionId, AggregateType = "Session".

Payload SessionEndedKafkaEvent

Snapshot завершённой сессии. Дом полей — entity: Session (раздел «Поля», «Доменные и интеграционные события»).

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
SessionIdGuidДаSession.IdImmutableКлюч агрегата (= Kafka key). Идентификатор завершённой сессии.
AnonymousIdGuidДаSession.AnonymousIdImmutableИдентификатор устройства/браузера гостя; тот же id владеет гостевыми записями favorites.
UserIdGuid?НетSession.UserIdImmutableПользователь связанной сессии; null — сессия осталась гостевой.
CreatedAtDateTimeOffsetДаSession.CreatedAtImmutableНачало сессии (первое действие устройства), время сервера. Нижняя граница Duration.
LastActivityAtDateTimeOffsetДаSession.LastActivityAtImmutableПоследняя активность (heartbeat/track), время сервера. Верхняя граница Duration.
DurationSecondsintДаSession.Duration (LastActivityAt − CreatedAt)ImmutableДлительность сессии в секундах, ≥ 0 (chk_sessions_duration_nonneg).

Вложенных моделей нет — payload плоский. Не входят в событие (правило session.md — «интеграционные события»): TenantId (контекст вне tenant, ADR-0052 п.2); Device.* (UserAgent, квази-PII IpAddress); EndedAt (диагностика лага polling-а, внутреннее); Status, LinkedAt.

Когда публикуется

УсловиеПереход состоянияСогласованность
activity-closer: now() − LastActivityAt > InactivityThreshold (10–15 мин, конфиг); выборка Active-сессий под блокировкой с пропуском занятыхActive → Inactiveзакрытие агрегата + событие в одном согласованном изменении; в нём же Duration = LastActivityAt − CreatedAt, EndedAt = now(), закрытие открытых PropertyObjectView (INV-SES-005)
Связывание: вход другого пользователя на устройстве с Active-сессией (UserId != actor, INV-SES-002) — форс-закрытие текущей сессии перед созданием новойActive → Inactiveто же

Событие терминальное: Inactive-сессия не изменяется никогда (INV-SES-006), поэтому на сессию приходится не более одной публикации. Дубликаты возможны только на уровне доставки (at-least-once) — снимаются идемпотентностью consumer-а по SessionId/MessageId.

Потребители (consumers)

Consumer-контекстConsumerSide effectsИдемпотентностьЗадержка (SLA)
dev-statsDevStatsSessionEndedConsumerСессионные метрики: длительность (DurationSeconds), число сессий, разрез гость/пользователь (по UserId != null) — ADR-0057first-seen по SessionId + MessageId< 5 мин

Других обязательных потребителей нет; топик открыт для будущих аналитических consumer-ов (доступ — m2m ACL Kafka).

Межтопиковый порядок. При форс-закрытии сессии (INV-SES-002) session-ended и property-object-view-ended её закрытых просмотров публикуются в одном согласованном изменении, но в разные топики — порядок доставки между ними не гарантирован. Consumer, читающий оба (dev-stats), не должен полагаться на приход одного раньше другого: каждое событие самодостаточно и несёт собственную длительность (DurationSeconds).

Версионирование и совместимость

  • ContractVersion = 1; при несовпадении версии consumer логирует warning и продолжает (upcasting/schema-migration нет). Версия в имени топика — по конвенции суффикс .v1.
  • Additive-only для minor: новые поля добавляются как optional с безопасным default — без bump.
  • Breaking (удаление/переименование/смена типа или смысла) — ContractVersion = 2 + новый топик pin.user-activity.session-ended.v2 + dual-publish на период миграции + deadline миграции consumers + sunset .v1.
  • Enum в payload нет (все поля скалярные); unknown-enum-обработка неприменима.
  • Порядок доставки не значим: событие терминальное и единичное на SessionId; Version-guard consumer-у не требуется (достаточно first-seen по SessionId).

Безопасность

ПолеКласс данныхПравило
SessionId, AnonymousId, UserId, CreatedAt, LastActivityAt, DurationSecondsInternalinclude
Device.IpAddressPII (квази)exclude — не входит в событие; в логах producer маскируется до /24
Device.UserAgentInternalexclude — не входит в событие

Actor/scope: событие публикует сам контекст (worker/связывание), не внешний актор; изоляция данных — record-level по UserId/AnonymousId (вне tenant, ADR-0052 п.2). Событие НЕ содержит secrets, tokens, raw credentials, email, полных PII/платёжных реквизитов, unbounded вложений.

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

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

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

  • Сущность (дом полей и инварианты): session.md; соседний контракт топика-семейства: session-linked.md.
  • Источники (producer): worker activity-closer, consumer identity-signed-in (связывание/форс-закрытие при входе).
  • Границы контекста и карта событий: ../README.md.
  • Решения: ADR-0052 (вне tenant), ADR-0054 (модуль sessions), ADR-0056 п.3 (механизм закрытия и Duration), ADR-0057 (dev-stats — потребитель).