POST /api/v1/roles/{roleId}/archive — Архивирование роли компании
Перевод роли Active → Archived с немедленным отзывом всех её активных назначений в одном согласованном
изменении (INV-R5 role.md). Роль не удаляется физически (мягкое удаление) — история назначений
сохраняется.
Назначение
Руководитель выводит роль из употребления (реорганизация). Все пользователи с этой ролью немедленно
теряют её права; consumers получают UserAccessChangedKafkaEvent на каждого затронутого. Owning-контекст
— identity. Feature group — roles.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | identity |
| API area | public |
| Feature group | roles |
| Статус | draft |
| Документ | docs/06-services/identity/api/company-user/archive-company-role.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (пользователь компании) |
| Auth policy / Permissions | identity.roles.manage (архив ролей включает каскадный отзыв назначений — identity.role-assignments.manage НЕ требуется отдельно: каскад легитимирован правом на роль, INV-R5) |
| Scope (RBAC) | организация: Active RoleAssignment со scope Platform или Organization(CompanyId) |
| Record-level | роль обязана принадлежать компании актора |
| Tenant isolation | TenantId = CompanyId |
| Audit | update + каскад: UpdatedByUserId/DeletedByUserId на роли, RevokedByUserId = actor на каждом назначении; события RoleArchivedEvent, RoleAssignmentRevokedEvent×N, UserAccessChangedKafkaEvent×N |
| MFA/approval | не требуется; операция обратима только созданием новой роли (восстановления из архива нет) |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/v1/roles/{roleId}/archive |
| Success status | 200 |
| Idempotency header | не применяется — повтор по архивной роли возвращает IDENTITY_ROLE_ARCHIVED |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
ActorUserId | identity claims | Guid | Да | Required | — |
CompanyId | identity claims (tenant_id) | Guid | Да | Required | Tenant isolation |
RoleId | route {roleId} | Guid | Да | != Guid.Empty | — |
Body отсутствует.
Модель ответа ArchiveRoleCommandResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | RoleDefinition.Id | — | Id роли. |
Status | string (enum RoleStatus) | Да | RoleDefinition.Status | — | Всегда Archived. |
RevokedAssignmentsCount | int | Да | число отозванных RoleAssignment | — | Сколько назначений отозвано каскадом. |
ArchivedAt | DateTimeOffset | Да | RoleDefinition.DeletedAt | — | Момент архивирования. |
Алгоритм
- Контекст и доступ. Операцию выполняет пользователь компании; tenant — его компания (
CompanyId); требуется правоidentity.roles.manageв scopeOrganization(CompanyId). Каскадный отзыв назначений легитимирован правом на роль — отдельныйidentity.role-assignments.manageне требуется (INV-R5). - Проверка входа и предусловий.
RoleIdнепустой; иначе —ValidationError.- Роль существует и принадлежит компании актора; иначе —
IDENTITY_ROLE_NOT_FOUND. - Роль не системная (INV-R1); системная —
IDENTITY_ROLE_SYSTEM_IMMUTABLE. - Роль в статусе
Active; ужеArchived—IDENTITY_ROLE_ARCHIVED(идемпотентный исход). - Защита от самоликвидации доступа: если каскадный отзыв лишил бы актора последнего назначения,
дающего
identity.roles.manageв этой организации, —IDENTITY_ROLE_LAST_ADMIN_PROTECTION(вычисляется по назначениям, загруженным на шаге 3, и правам актора).
- Что читаем. Роль (для проверок выше) и все её активные назначения (
RoleAssignmentсоStatus = Active) — полным списком: для каждого затронутого пользователя нуженUserIdдля рассылки события, объём ограничен размером компании. Дополнительно — назначения актора для guard-а «последний админ». - Бизнес-правила и переходы.
- Роль переходит
Active → Archived(INV-R5): фиксируются момент архивирования и автор; поднимается доменный фактRoleArchivedEvent. - Каждое активное назначение роли переходит
Active → Revokedс причиной «роль архивирована»: фиксируются момент и автор отзыва; поднимаетсяRoleAssignmentRevokedEventна каждое.
- Роль переходит
- Что меняется. Обновляются роль и все её активные назначения (батч). Конкурентный конфликт на любой
записи роняет операцию целиком — повтор безопасен (
IDENTITY_CONCURRENCY_CONFLICT). - Какие факты/события фиксируются. На каждого затронутого пользователя публикуется
UserAccessChangedKafkaEvent(топикpin.identity.access.v1, ключUserId) с пересобранным списком оставшихся активных назначений{RoleCode, ScopeType, ScopeIds}и версией. При более чем 100 затронутых пользователях fan-out делегируется background-джобе батчами: вместо N событий фиксируется маркер задачи (role.md «События»). - Согласованность. Роль, все назначения и события фиксируются в одном согласованном изменении — это гарантирует INV-R5 «права отзываются немедленно».
- Результат. Идентификатор роли, статус
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_FOUND | 404 | Роль не существует / чужая | «Роль не найдена» | no retry |
IDENTITY_ROLE_SYSTEM_IMMUTABLE | 409 | IsSystem = true (INV-R1) | «Системная роль неизменяема» | no retry |
IDENTITY_ROLE_ARCHIVED | 409 | Уже Archived | «Роль уже архивирована» | no retry (идемпотентный исход) |
IDENTITY_ROLE_LAST_ADMIN_PROTECTION | 409 | Каскад лишил бы актора права управления ролями | «Нельзя архивировать роль, дающую вам права администратора» | no retry |
IDENTITY_CONCURRENCY_CONFLICT | 409 | Конфликт оптимистичной блокировки на роли/назначении | «Повторите операцию» | retry |
FORBIDDEN | 403 | Нет 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, ThenIDENTITY_ROLE_ARCHIVED(409, идемпотентный исход), изменений нет. - Given системная роль (
IsSystem = true, INV-R1), When archive, ThenIDENTITY_ROLE_SYSTEM_IMMUTABLE(409). - Given роль чужой компании или несуществующая, When archive, Then
IDENTITY_ROLE_NOT_FOUND(404). - Given каскад лишил бы актора последнего назначения, дающего
identity.roles.manageв организации, When archive, ThenIDENTITY_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, ThenFORBIDDEN(403).
Совместимость и наблюдаемость
Восстановление из архива
Восстановление не поддерживается — вернуть роль в Active этим методом нельзя, только создать новую.
Введение Unarchive будет additive-операцией с ревизией INV-R5.
- Logs:
RoleId,CompanyId,RevokedAssignmentsCount; metrics:identity_role_archive_total{result},identity_access_fanout_sizehistogram; traces: OTel; dashboardidentity-overview; alert: fan-out-job лаг; runbook: access fan-out — Pin.Identity.Background.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (4): GET /api/v1/roles — Список ролей компании, Identity Company-User API — реестр методов и общие правила области, POST /api/v1/role-assignments/{assignmentId}/revoke — Отзыв назначения роли, PUT /api/v1/roles/{roleId} — Обновление роли компании
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность: domain/role.md (INV-R1, INV-R5, stateDiagram назначений).
- Событие доступов: role.md «События» (
UserAccessChangedKafkaEvent). - Отзыв единичного назначения: revoke-role-assignment.md.