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

Event Contract: pin.identity.user-access-changed (UserAccessChangedKafkaEvent)

Версионируемый контракт интеграционного события «эффективные права пользователя изменились». Consumer-документы ссылаются сюда, не переописывая payload. Покрывает минимальные события role-assigned и role-revoked: вместо пары точечных событий публикуется полный snapshot активных назначений пользователя с причиной изменения — потребителю не нужно реконструировать состояние из дельт (устойчиво к потере/переупорядочиванию отдельных событий).

Назначение

Инвалидация кэшей авторизации: любое изменение эффективных прав пользователя (grant/revoke/expire RoleAssignment, изменение состава permissions роли, архив роли, архив пользователя) требует сбросить кэшированные permission-set-ы у gateway и сервисов-enforcement-ов. Событие несёт список активных назначений (RoleCode + scope), по которому потребитель может пересобрать права локально через свой кэш каталога ролей либо просто инвалидировать ключ.

Metadata

ПолеЗначение
Event typepin.identity.user-access-changed
Topicpin.identity.access.v1
Partitions / key6 / key = UserId (порядок снапшотов per-user; последний выигрывает)
ContractVersion1
Produceridentity: grant/revoke RoleAssignment (public API ролей, admin API), background-job экспирации (ExpiresAt), fan-out при изменении состава permissions роли (батчами по всем пользователям роли), talent-identity-sync (дефолтное назначение / отзыв при архиве)
Статусdraft
Атомарность и доставка

Событие публикуется атомарно со сменой RoleAssignment/RoleDefinition: до фиксации изменения событие не отправляется, после — доставляется гарантированно (at-least-once). Fan-out по роли идёт batch-заданием: каждый батч — своё согласованное изменение и своё событие на пользователя.

Payload UserAccessChangedKafkaEvent

Дом полей — entity: RoleDefinition/RoleAssignment (раздел «Доменные и интеграционные события»: UserId, CompanyId?, список активных назначений {RoleCode, ScopeType, ScopeIds}, Version).

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
UserIdGuidДаRoleAssignment.UserIdImmutableПользователь, чьи права изменились (= Kafka key).
CompanyIdGuid?НетUserEntity.CompanyIdAdditive onlyКомпания пользователя; null — инвестор.
ChangeReasonstring (enum AccessChangeReason)Давычисляется при формировании событияAdditive only (новые значения)Что произошло: см. enum ниже.
ActiveAssignmentsIReadOnlyList<ActiveRoleAssignmentSnapshot>Да (может быть пустым — все права отозваны)активные назначения пользователя (Status = Active) на момент фиксации измененияAdditive onlyПолный snapshot активных назначений после изменения.
VersionlongДаmax(RoleAssignment.Version) затронутого назначения; для fan-out — RoleDefinition.VersionImmutable semanticsМонотонный guard: потребитель хранит последний применённый (UserId, Version) и отбрасывает меньшие.

Вложенная модель ActiveRoleAssignmentSnapshot

Проекция RoleAssignment + ResourceScope (дом — role.md).

ПолеТипОбязательностьОграниченияОписание
AssignmentIdGuidДаRoleAssignment.Id.
RoleIdGuidДаRoleDefinition.Id.
RoleCodestringДа^[a-z][a-z0-9-]{1,62}$Машинный код роли (agent, developer-manager, …).
ScopeTypestring (enum ResourceScopeType)ДаPlatform / Organization / ProjectGroup / ProjectТип scope назначения.
ScopeOrganizationIdGuid?Обязателен при ScopeType ∈ {Organization, ProjectGroup, Project}Компания scope.
ScopeProjectGroupIdGuid?Обязателен при ScopeType = ProjectGroupid из chessboard (без FK)Группа проектов.
ScopeProjectIdGuid?Обязателен при ScopeType = Projectid из chessboard (без FK)Проект.
ExpiresAtDateTimeOffset?НетСрок действия временного назначения.
Состав permissions не в payload

Состав permissions роли в payload не входит (роли компаний мутируют независимо): потребитель резолвит RoleCode/RoleId → permissions своим кэшем каталога ролей identity (интроспекция internal-API) либо инвалидирует весь кэш пользователя.

Enum AccessChangeReason

ЗначениеТриггер
AssignmentGrantedRoleAssignment создан (grant; в т.ч. дефолтное назначение синка/регистрации).
AssignmentRevokedRoleAssignment отозван (revoke актором, каскад архива роли INV-R5, архив пользователя).
AssignmentExpiredBackground-job перевёл назначение в Expired по ExpiresAt.
RolePermissionsChangedИзменён состав permissions роли — fan-out по всем пользователям с активным назначением этой роли.

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

УсловиеПереход состоянияПубликация
Grant назначения (public API ролей / admin API / синк / регистрация)RoleAssignment: [*] → Activeатомарно с созданием назначения
Revoke назначения / архив пользователя / каскад архива RoleDefinition (INV-R5)Active → Revokedта же (батч-отзыв — один снапшот на пользователя)
Job экспирацииActive → Expiredбатч job-а: по событию на пользователя
Изменение состава permissions роли (grant/revoke permission)без перехода назначениймутация роли атомарна; fan-out — background-job батчами по 500 пользователей, идемпотентный (повтор батча — тот же снапшот)

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

Сверено с role.md («consumers (crm, catalog, chessboard, gateway) сбрасывают кэш авторизации»).

Consumer-контекстЭффектИдемпотентностьЗадержка (SLA)
gateway (gateway-shared)Сброс кэша интроспекции effectivePermissions(userId, *)(UserId, Version)-guard< 30 сек
crmСброс кэша прав (видимость лидов/фиксаций/сделок, scope-фильтры)то же< 1 мин
catalogСброс кэша прав admin-фич витрины (оверрайды/продвижение)то же< 1 мин
chessboardСброс кэша прав по scope проектов (Organization/ProjectGroup/Project)то же< 1 мин

Порядок и согласованность

  • Snapshot — замещение. Потребитель замещает кэшированный набор активных назначений целиком списком ActiveAssignments; пустой список = все права отозваны (кэш прав пользователя очищается). Дельты не применяются — событие самодостаточно (self-healing к пропущенным/переупорядоченным событиям).
  • Разные источники версии. Для прямого grant/revoke Version = версия назначения, для fan-out изменения роли — версия RoleDefinition (разные пространства версий). Guard (UserId, Version) защищает от out-of-order внутри источника; при неоднозначности порядка между источниками безопасное поведение — полная инвалидация кэша прав пользователя (то же, что fallback на неизвестный enum), что ограничивает устаревание ближайшим резолвом.
  • Окно эффекта. Отзыв прав становится эффективным на потребителях в пределах их SLA (gateway — < 30 сек): в этом окне отозванное право может ещё срабатывать. Для критичных отзывов, где окно неприемлемо (блокировка пользователя), действует отдельный жёсткий путь user-blocked + отзыв сессий в транзакции.

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

  • ContractVersion = 1; несоответствие версии события — warn-and-proceed.
  • Additive-only: новые optional-поля snapshot-а — без bump; breaking — ContractVersion = 2 + топик pin.identity.access.v2 + dual-publish + sunset.
  • Unknown enum (ChangeReason/ScopeType) — fallback: полная инвалидация кэша пользователя (безопасное поведение), warning.
  • Снапшот-семантика — потребитель всегда может применить последнее событие независимо от пропущенных (self-healing; guard только от out-of-order).

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

ПолеКласс данныхПравило
UserId, CompanyId, Version, ChangeReasonInternalinclude
ActiveAssignments.*Internal (модель доступа — чувствительная информация о полномочиях)include; доступ к топику — только m2m ACL Kafka (gateway/crm/catalog/chessboard); в пользовательские UI не транслируется

Событие НЕ содержит PII, secrets, состава permissions (резолв — интроспекцией).

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

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

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

  • Сущности: role.md (RoleAssignment/ResourceScope, INV-R5, stateDiagram), permission.md (каталог, резолв кодов).
  • Источники: public API ролей (README identity), admin API назначений платформенных ролей, talent-identity-sync, job экспирации назначений.