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

PUT /api/v1/roles/{roleId} — Обновление роли компании

Изменение атрибутов роли компании: Name, Description. Код (Code) иммутабелен; состав permissions меняется отдельной операцией set-role-permissions.md; архив — archive-company-role.md. Системные роли неизменяемы (INV-R1).

Назначение

Руководитель переименовывает/уточняет описание внутренней роли своей компании. Owning-контекст — identity (дом полей — domain/role.md). Feature group — roles.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (пользователь компании)
Auth policy / Permissionsidentity.roles.manage
Scope (RBAC)организация: Active RoleAssignment со scope Platform или Organization(CompanyId)
Record-levelроль обязана принадлежать компании актора (role.CompanyId == CompanyId)
Tenant isolationTenantId = CompanyId; INullableTenantedEntity-интерсептор + явный guard
Auditupdate: UpdatedByUserId, Version += 1, доменное событие RoleUpdatedEvent

HTTP-контракт

ПолеЗначение
MethodPUT
Route/api/v1/roles/{roleId}
Success status200
Idempotency headerне применяется (PUT идемпотентен + Version-сверка)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ActorUserIdidentity claimsGuidДаRequired
CompanyIdidentity claims (tenant_id)GuidДаRequiredTenant isolation
RoleIdroute {roleId}GuidДа!= Guid.Empty
NamebodystringДа1..128
Descriptionbodystring?Нет<= 512
VersionbodylongДа>= 1; сверка с RoleDefinition.Version
примечание
Почему нет Code в body

Code в body отсутствует намеренно — код роли иммутабелен после создания (аналогия INV-P2).

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

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаRoleDefinition.IdId роли.
NamestringДаRoleDefinition.NameНовое название.
VersionlongДаRoleDefinition.VersionНовая версия.
UpdatedAtDateTimeOffsetДаRoleDefinition.UpdatedAtМомент сохранения.

Предусловия и постусловия

Предусловия. Актор аутентифицирован и имеет identity.roles.manage в scope Organization(CompanyId); роль существует, принадлежит компании актора, не системная (INV-R1) и Active; переданная Version совпадает с текущей.

Постусловия при успехе. Обновлены Name/Description роли, версия увеличена, зафиксирован автор; поднят доменный факт RoleUpdatedEvent. Интеграционных событий нет — переименование не меняет эффективных прав, поэтому UserAccessChangedKafkaEvent не публикуется. Code остаётся прежним (иммутабелен).

Постусловия при отказе. Атрибуты и версия роли не изменились.

Алгоритм

  1. Контекст и доступ. Операцию выполняет пользователь компании; tenant — его компания (CompanyId); требуется право identity.roles.manage в scope Organization(CompanyId).
  2. Проверка входа и предусловий.
    • Синтаксис: Name 1..128, Description ≤ 512; иначе — ValidationError.
    • Роль существует и принадлежит компании актора; системная (CompanyId == null) или чужая → IDENTITY_ROLE_NOT_FOUND (без раскрытия существования чужих ролей).
    • Роль не системная (INV-R1); системная — IDENTITY_ROLE_SYSTEM_IMMUTABLE.
    • Роль в статусе Active; ArchivedIDENTITY_ROLE_ARCHIVED.
    • Оптимистичная блокировка: переданная Version совпадает с версией роли; иначе — IDENTITY_CONCURRENCY_CONFLICT.
  3. Что читаем. Роль (для проверок выше); состав прав не нужен.
  4. Бизнес-правила и маппинг. Обновляются Name и Description (код Code иммутабелен); версия роли увеличивается, фиксируется автор изменения. Поднимается доменный факт RoleUpdatedEvent.
  5. Что меняется. Обновляется роль. Конкурентный конфликт → IDENTITY_CONCURRENCY_CONFLICT.
  6. Согласованность. Интеграционных событий нет: переименование не меняет эффективных прав (UserAccessChangedKafkaEvent публикуется только при grant/revoke и изменении состава прав, role.md). Роль и доменный факт RoleUpdatedEvent фиксируются в одном согласованном изменении.
  7. Результат. Идентификатор роли, новое название, новая версия, момент сохранения (200).

Диаграмма

Ошибки и failure modes

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

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

  • Additive-изменения формы допустимы; появление возможности менять Code — breaking (отдельный ADR).
  • Logs: CompanyId, RoleId, ActorUserId; metrics: identity_role_update_total{result}; traces: OTel; dashboard identity-overview; runbook — не применяется.

Acceptance Criteria

  • Given роль компании Active и актуальная Version, When PUT с новыми Name/Description, Then 200; атрибуты обновлены, версия увеличена; RoleUpdatedEvent зафиксирован; UserAccessChangedKafkaEvent не публикуется (права не изменились).
  • Given попытка передать Code в теле, When PUT, Then поле игнорируется — Code остаётся прежним.
  • Given роль IsSystem = true, When PUT, Then IDENTITY_ROLE_SYSTEM_IMMUTABLE (409) — INV-R1.
  • Given роль другой компании или CompanyId = null, When PUT, Then IDENTITY_ROLE_NOT_FOUND (404) — существование чужих ролей не раскрывается.
  • Given переданная Version не совпадает с текущей, When PUT, Then IDENTITY_CONCURRENCY_CONFLICT (409); роль не изменена.
  • Given роль Archived, When PUT, Then IDENTITY_ROLE_ARCHIVED (409).
  • Given Name пустой или длиннее 128 символов, When PUT, Then ValidationError (400).
  • Given актор без identity.roles.manage, When PUT, Then FORBIDDEN (403).

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

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

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