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 type | pin.identity.user-access-changed |
| Topic | pin.identity.access.v1 |
| Partitions / key | 6 / key = UserId (порядок снапшотов per-user; последний выигрывает) |
| ContractVersion | 1 |
| Producer | identity: 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 | Описание |
|---|---|---|---|---|---|
UserId | Guid | Да | RoleAssignment.UserId | Immutable | Пользователь, чьи права изменились (= Kafka key). |
CompanyId | Guid? | Нет | UserEntity.CompanyId | Additive only | Компания пользователя; null — инвестор. |
ChangeReason | string (enum AccessChangeReason) | Да | вычисляется при формировании события | Additive only (новые значения) | Что произошло: см. enum ниже. |
ActiveAssignments | IReadOnlyList<ActiveRoleAssignmentSnapshot> | Да (может быть пустым — все права отозваны) | активные назначения пользователя (Status = Active) на момент фиксации изменения | Additive only | Полный snapshot активных назначений после изменения. |
Version | long | Да | max(RoleAssignment.Version) затронутого назначения; для fan-out — RoleDefinition.Version | Immutable semantics | Монотонный guard: потребитель хранит последний применённый (UserId, Version) и отбрасывает меньшие. |
Вложенная модель ActiveRoleAssignmentSnapshot
Проекция RoleAssignment + ResourceScope (дом — role.md).
| Поле | Тип | Обязательность | Ограничения | Описание |
|---|---|---|---|---|
AssignmentId | Guid | Да | — | RoleAssignment.Id. |
RoleId | Guid | Да | — | RoleDefinition.Id. |
RoleCode | string | Да | ^[a-z][a-z0-9-]{1,62}$ | Машинный код роли (agent, developer-manager, …). |
ScopeType | string (enum ResourceScopeType) | Да | Platform / Organization / ProjectGroup / Project | Тип scope назначения. |
ScopeOrganizationId | Guid? | Обязателен при ScopeType ∈ {Organization, ProjectGroup, Project} | — | Компания scope. |
ScopeProjectGroupId | Guid? | Обязателен при ScopeType = ProjectGroup | id из chessboard (без FK) | Группа проектов. |
ScopeProjectId | Guid? | Обязателен при ScopeType = Project | id из chessboard (без FK) | Проект. |
ExpiresAt | DateTimeOffset? | Нет | — | Срок действия временного назначения. |
Состав permissions роли в payload не входит (роли компаний мутируют независимо): потребитель
резолвит RoleCode/RoleId → permissions своим кэшем каталога ролей identity (интроспекция
internal-API) либо инвалидирует весь кэш пользователя.
Enum AccessChangeReason
| Значение | Триггер |
|---|---|
AssignmentGranted | RoleAssignment создан (grant; в т.ч. дефолтное назначение синка/регистрации). |
AssignmentRevoked | RoleAssignment отозван (revoke актором, каскад архива роли INV-R5, архив пользователя). |
AssignmentExpired | Background-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, ChangeReason | Internal | include |
ActiveAssignments.* | Internal (модель доступа — чувствительная информация о полномочиях) | include; доступ к топику — только m2m ACL Kafka (gateway/crm/catalog/chessboard); в пользовательские UI не транслируется |
Событие НЕ содержит PII, secrets, состава permissions (резолв — интроспекцией).
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity / Aggregate: NotificationEntity
- API (6): DELETE /api/admin/v1/users/{userId} — Удаление пользователя: архив vs полное удаление (admin), Identity Admin API — реестр методов и общие правила области, Identity Agent API — реестр методов и общие правила области, Identity Company-User API — реестр методов и общие правила области, POST /api/v1/me/become-agent — Инвестор становится агентом, POST /api/v1/me/become-investor — Агент возвращается к роли инвестора
- Use Cases (1): UC-IDN-011. Инвестор становится агентом
- События (1): Event Contract: pin.identity.agent-membership-changed (AgentMembershipChangedKafkaEvent)
- Консюмеры (3): Consumer: Chessboard Project Deleted (pin-identity-chessboard-projects), Consumer: Identity Events → Notifications (notifications-identity-events), Consumer: Talent Identity Sync (pin-identity-talent-sync)
- Фоновые задачи (3): Background Job: AccessFanOutJob — веерная рассылка UserAccessChangedKafkaEvent при смене состава прав роли, Background Job: ExpireRoleAssignmentsJob, Background Job: talent-initial-import (первичная загрузка / bootstrap из Talent Identity)
- index.md (1): Identity Service (Pin.Identity)
- registries (1): Реестр событий PIN
Связанные документы
- Сущности: role.md (RoleAssignment/ResourceScope, INV-R5, stateDiagram), permission.md (каталог, резолв кодов).
- Источники: public API ролей (README identity), admin API назначений платформенных ролей, talent-identity-sync, job экспирации назначений.