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

POST /api/v1/roles/{roleId}/archive — Архивирование роли компании

Перевод роли Active → Archived с немедленным отзывом всех её активных назначений в одном согласованном изменении (INV-R5 role.md). Роль не удаляется физически (мягкое удаление) — история назначений сохраняется.

Назначение

Руководитель выводит роль из употребления (реорганизация). Все пользователи с этой ролью немедленно теряют её права; consumers получают UserAccessChangedKafkaEvent на каждого затронутого. Owning-контекст — identity. Feature group — roles.

Metadata

ПолеЗначение
Сервис/контекстidentity
API areapublic
Feature grouproles
Статусdraft
Документdocs/06-services/identity/api/company-user/archive-company-role.md

Актор и доступ

ПроверкаЗначение
Actor typeUser (пользователь компании)
Auth policy / Permissionsidentity.roles.manage (архив ролей включает каскадный отзыв назначений — identity.role-assignments.manage НЕ требуется отдельно: каскад легитимирован правом на роль, INV-R5)
Scope (RBAC)организация: Active RoleAssignment со scope Platform или Organization(CompanyId)
Record-levelроль обязана принадлежать компании актора
Tenant isolationTenantId = CompanyId
Auditupdate + каскад: UpdatedByUserId/DeletedByUserId на роли, RevokedByUserId = actor на каждом назначении; события RoleArchivedEvent, RoleAssignmentRevokedEvent×N, UserAccessChangedKafkaEvent×N
MFA/approvalне требуется; операция обратима только созданием новой роли (восстановления из архива нет)

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/roles/{roleId}/archive
Success status200
Idempotency headerне применяется — повтор по архивной роли возвращает IDENTITY_ROLE_ARCHIVED
Correlationсквозной trace через заголовок traceparent

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ActorUserIdidentity claimsGuidДаRequired
CompanyIdidentity claims (tenant_id)GuidДаRequiredTenant isolation
RoleIdroute {roleId}GuidДа!= Guid.Empty

Body отсутствует.

Модель ответа ArchiveRoleCommandResult

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаRoleDefinition.IdId роли.
Statusstring (enum RoleStatus)ДаRoleDefinition.StatusВсегда Archived.
RevokedAssignmentsCountintДачисло отозванных RoleAssignmentСколько назначений отозвано каскадом.
ArchivedAtDateTimeOffsetДаRoleDefinition.DeletedAtМомент архивирования.

Алгоритм

  1. Контекст и доступ. Операцию выполняет пользователь компании; tenant — его компания (CompanyId); требуется право identity.roles.manage в scope Organization(CompanyId). Каскадный отзыв назначений легитимирован правом на роль — отдельный identity.role-assignments.manage не требуется (INV-R5).
  2. Проверка входа и предусловий.
    • RoleId непустой; иначе — ValidationError.
    • Роль существует и принадлежит компании актора; иначе — IDENTITY_ROLE_NOT_FOUND.
    • Роль не системная (INV-R1); системная — IDENTITY_ROLE_SYSTEM_IMMUTABLE.
    • Роль в статусе Active; уже ArchivedIDENTITY_ROLE_ARCHIVED (идемпотентный исход).
    • Защита от самоликвидации доступа: если каскадный отзыв лишил бы актора последнего назначения, дающего identity.roles.manage в этой организации, — IDENTITY_ROLE_LAST_ADMIN_PROTECTION (вычисляется по назначениям, загруженным на шаге 3, и правам актора).
  3. Что читаем. Роль (для проверок выше) и все её активные назначения (RoleAssignment со Status = Active) — полным списком: для каждого затронутого пользователя нужен UserId для рассылки события, объём ограничен размером компании. Дополнительно — назначения актора для guard-а «последний админ».
  4. Бизнес-правила и переходы.
    • Роль переходит Active → Archived (INV-R5): фиксируются момент архивирования и автор; поднимается доменный факт RoleArchivedEvent.
    • Каждое активное назначение роли переходит Active → Revoked с причиной «роль архивирована»: фиксируются момент и автор отзыва; поднимается RoleAssignmentRevokedEvent на каждое.
  5. Что меняется. Обновляются роль и все её активные назначения (батч). Конкурентный конфликт на любой записи роняет операцию целиком — повтор безопасен (IDENTITY_CONCURRENCY_CONFLICT).
  6. Какие факты/события фиксируются. На каждого затронутого пользователя публикуется UserAccessChangedKafkaEvent (топик pin.identity.access.v1, ключ UserId) с пересобранным списком оставшихся активных назначений {RoleCode, ScopeType, ScopeIds} и версией. При более чем 100 затронутых пользователях fan-out делегируется background-джобе батчами: вместо N событий фиксируется маркер задачи (role.md «События»).
  7. Согласованность. Роль, все назначения и события фиксируются в одном согласованном изменении — это гарантирует INV-R5 «права отзываются немедленно».
  8. Результат. Идентификатор роли, статус Archived, число отозванных назначений и момент архивирования. Кэш эффективных прав затронутых пользователей (perm:{userId}) инвалидируется после фиксации (best-effort; консистентность добивает событие).

Диаграмма

Побочные эффекты

ТипДетали
Интеграционные событияUserAccessChangedKafkaEvent ×N → pin.identity.access.v1 (потребители: crm, catalog, chessboard, gateway — сброс кэшей авторизации)
Проекции / кэшкэш эффективных прав perm:{userId} затронутых пользователей — инвалидация после фиксации

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
IDENTITY_ROLE_NOT_FOUND404Роль не существует / чужая«Роль не найдена»no retry
IDENTITY_ROLE_SYSTEM_IMMUTABLE409IsSystem = true (INV-R1)«Системная роль неизменяема»no retry
IDENTITY_ROLE_ARCHIVED409Уже Archived«Роль уже архивирована»no retry (идемпотентный исход)
IDENTITY_ROLE_LAST_ADMIN_PROTECTION409Каскад лишил бы актора права управления ролями«Нельзя архивировать роль, дающую вам права администратора»no retry
IDENTITY_CONCURRENCY_CONFLICT409Конфликт оптимистичной блокировки на роли/назначении«Повторите операцию»retry
FORBIDDEN403Нет identity.roles.manage«Недостаточно прав»no retry

Acceptance Criteria

  • Given активная роль компании с 5 активными назначениями, When archive, Then роль Active → Archived (DeletedAt, автор), все 5 назначений Active → Revoked, ответ RevokedAssignmentsCount = 5; на каждого затронутого пользователя публикуется UserAccessChangedKafkaEvent (key = UserId) с пересобранным списком оставшихся активных назначений.
  • Given роль уже Archived, When archive, Then IDENTITY_ROLE_ARCHIVED (409, идемпотентный исход), изменений нет.
  • Given системная роль (IsSystem = true, INV-R1), When archive, Then IDENTITY_ROLE_SYSTEM_IMMUTABLE (409).
  • Given роль чужой компании или несуществующая, When archive, Then IDENTITY_ROLE_NOT_FOUND (404).
  • Given каскад лишил бы актора последнего назначения, дающего identity.roles.manage в организации, When archive, Then IDENTITY_ROLE_LAST_ADMIN_PROTECTION (409), изменений нет.
  • Given роль с > 100 активными назначениями, When archive, Then роль и назначения меняются атомарно, fan-out прав делегируется background-джобе батчами (маркер задачи вместо N синхронных событий).
  • Given конкурентный конфликт на роли или любом её назначении, When archive, Then IDENTITY_CONCURRENCY_CONFLICT (409) — операция роняется целиком, повтор безопасен.
  • Given нет identity.roles.manage в scope организации, When archive, Then FORBIDDEN (403).

Совместимость и наблюдаемость

Восстановление из архива

Восстановление не поддерживается — вернуть роль в Active этим методом нельзя, только создать новую. Введение Unarchive будет additive-операцией с ревизией INV-R5.

  • Logs: RoleId, CompanyId, RevokedAssignmentsCount; metrics: identity_role_archive_total{result}, identity_access_fanout_size histogram; traces: OTel; dashboard identity-overview; alert: fan-out-job лаг; runbook: access fan-out — Pin.Identity.Background.

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

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

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

  • Сущность: domain/role.md (INV-R1, INV-R5, stateDiagram назначений).
  • Событие доступов: role.md «События» (UserAccessChangedKafkaEvent).
  • Отзыв единичного назначения: revoke-role-assignment.md.