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

POST /api/v1/me/become-agent — Инвестор становится агентом

Инвестор самостоятельно становится агентом (ADR-0061). Два режима: независимый (по умолчанию) — роль agent в scope Platform, без агентства; через приглашение (invitationId в теле) — вступление в агентство, эквивалент accept-invitation.md. Учётка и UserId сохраняются — новая учётка не создаётся.

Назначение

Точка входа «стать агентом» из личного кабинета инвестора. Вызывается инвестором; на выходе — та же учётка с типом Agent, назначенной ролью agent и (в режиме через приглашение) членством в агентстве. Owning-контекст — identity.

Metadata

ПолеЗначение
Сервис/контекстidentity
API areainvestor
Feature groupagent-onboarding
Статусapproved
Документdocs/06-services/identity/api/investor/become-agent.md

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

ПроверкаЗначение
Actor typeUser (инвестор)
Auth policy / Permissionsauthenticated; self-действие (UserId из sub)
Scope (RBAC)self; независимый режим выдаёт роль agent в scope Platform (INV-R3)
Record-levelтолько собственная учётка
Tenant isolationне применяется (независимый агент — вне tenant, ADR-0052); режим через приглашение — как accept
Auditupdate user (Type = Agent); create назначение роли (agent, Platform); при invite-режиме — как accept-invitation.md
MFA/approvalне требуется

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/me/become-agent
Success status200
Idempotency headerIdempotency-Key (Guid)
Correlationсквозной trace через traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ActorUserIdidentity claims (sub)GuidДавалидный JWT401 без токена
InvitationIdbodyGuid?Нетпри заданном — существующее Pending-приглашение на телефон актора
IdempotencyKeyheader Idempotency-KeyGuidДаRequired

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

ПолеТипОбязательностьИсточникMaskingОписание
UserIdGuidДаUserEntity.IdУчётка (не меняется).
UserTypestring (enum UserType)ДаUserEntity.TypeAgent.
Modestring (enum BecomeAgentMode)ДаcomputedIndependent / JoinedAgency.
CompanyIdGuid?НетAgentMembership.CompanyIdАгентство (при JoinedAgency); null для независимого.
RoleAssignmentIdGuidДаRoleAssignment.IdНазначение роли agent.

Алгоритм

  1. Контекст и доступ. Операцию выполняет аутентифицированный инвестор (ActorUserId из токена). Повтор с тем же Idempotency-Key возвращает результат первого вызова.
  2. Проверка входа. Синтаксис корректен — иначе ValidationError; актор аутентифицирован — иначе 401.
  3. Проверки и ветвление. Получить пользователя-актора; он Active — иначе IDENTITY_USER_BLOCKED.
    • InvitationId задан → выполнить сценарий принятия приглашения (см. accept-invitation.md, шаги 3–6: INV-CI2 совпадение телефона, INV-AM1 нет активного членства) → Mode = JoinedAgency;
    • InvitationId не задан (независимый режим): если актор уже Agent с активным членством — IDENTITY_AGENT_ALREADY_IN_AGENCY (для независимого повтор — идемпотентный no-op); взять системную роль agent.
  4. Переходы и маппинг (независимый режим). Пользователь: Type Investor → Agent, зеркало CompanyId = null (INV-2 user.md); создаётся назначение роли agent в scope Platform — допустимо для agent (INV-R3).
  5. Что меняется. Независимый режим: в одном согласованном изменении обновляется пользователь и создаётся назначение роли.
  6. События. Независимый режим: UserUpsertedKafkaEventpin.identity.users.v1 (Type = Agent, CompanyId = null); UserAccessChangedKafkaEventpin.identity.access.v1. Membership-события в независимом режиме не публикуются (записи членства нет). Режим через приглашение публикует события как accept-invitation.md.
  7. Результат. UserId (не меняется), UserType = Agent, Mode, CompanyId?, идентификатор назначения роли; HTTP 200.

Диаграмма

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

ТипДетали
Интеграционные событияUserUpsertedKafkaEvent; UserAccessChangedKafkaEvent; при вступлении в агентство дополнительно AgentMembershipChangedKafkaEvent + CompanyInvitationChangedKafkaEvent
Внешние вызовыnone
Проекции / поискpermission-кэш агента прогреется на следующей проверке
Уведомленияnone (независимый режим); при вступлении — как accept
Окно eventual-consistency

После коммита пользователь сразу становится Agent и видит агентский интерфейс; назначение роли agent действует со следующей проверки прав (прогрев perm:{userId}). Потребители снапшота pin.identity.users.v1 (crm/referral/messenger) увидят Type = Agent через секунды после доставки события; до этого их реплики держат прежний тип. В независимом режиме членства нет, поэтому ростер агентства не затрагивается.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Битый InvitationIdСписок ошибокno retry
IDENTITY_USER_BLOCKED400Status != Active«Учётная запись заблокирована»no retry
IDENTITY_AGENT_ALREADY_IN_AGENCY409Уже агент с активным членством (INV-AM1)«Вы уже агент агентства»no retry
IDENTITY_INVITATION_*403/404/409В режиме через приглашение — как accept-invitation.mdсм. acceptno retry
CONCURRENCY_CONFLICT409Гонка (оптимистическая блокировка)«Данные изменены, повторите»retry после перечитки

Acceptance Criteria

  • Given активный инвестор без активного членства и без invitationId, When become-agent, Then TypeAgent, зеркало CompanyId = null, создаётся назначение agent@Platform, Mode = Independent, уходят UserUpsertedKafkaEvent (Type = Agent, CompanyId = null) и UserAccessChangedKafkaEvent; membership-события не публикуются.
  • Given в теле задан invitationId на Pending-приглашение с телефоном актора, When become-agent, Then отрабатывает сценарий принятия приглашения (см. accept-invitation.md), Mode = JoinedAgency, CompanyId = агентство.
  • Given актор уже Agent с активным членством, invitationId не задан, When become-agent, Then IDENTITY_AGENT_ALREADY_IN_AGENCY (409).
  • Given независимый режим и повтор с тем же Idempotency-Key, When become-agent, Then возвращается результат первого вызова (идемпотентный no-op), второго назначения роли нет.
  • Given Status != Active (заблокирован/архив), When become-agent, Then IDENTITY_USER_BLOCKED (400) и учётка не меняется.
  • Given invitationId указывает на приглашение с чужим телефоном / истёкшее / не Pending, When become-agent, Then соответствующий IDENTITY_INVITATION_* (403/404/409), как в accept.

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

  • Новый эндпоинт, additive. Контракты событий — версия контракта 1.
  • Logs: ActorUserId, Mode, CompanyId?; metrics: identity_become_agent_total{mode,result}; traces: OTel.

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

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

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