UC-IDN-011. Инвестор становится агентом
Назначение
Самостоятельный переход инвестора в агенты (ADR-0061): без приглашения инвестор становится независимым
агентом — добавляется роль agent в scope Platform, тип меняется Investor → Agent, членства в
агентстве нет. Учётка и UserId сохраняются — новая учётка не создаётся. Если у инвестора есть приглашение
(invitationId), сценарий эквивалентен вступлению в агентство (UC-IDN-009, ветка accept). Вызывает сам
инвестор. Связан с контекстом identity.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-IDN-011 |
| Контекст | identity (см. docs/06-services/identity/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Инвестор (сам) |
| Permissions / scope | self (UserId из sub); независимый режим выдаёт роль agent в scope Platform (INV-R3) |
| Tenant | не применяется — независимый агент вне tenant (ADR-0052) |
| Record-level | собственная учётка |
Предусловие
UserEntity.Type = Investor,Status = Active.- Для независимого режима — приглашение не требуется; для вступления в агентство нужен
Pending-инвайт на телефон инвестора.
Триггер
| Поле | Значение |
|---|---|
| Источник | UI button |
| Триггерящий элемент | ЛК инвестора → «Стать агентом» |
Основной Поток
- Инвестор нажимает «Стать агентом» (без приглашения — независимый режим).
- Система принимает запрос (become-agent.md, тело без
invitationId). - Проверить: пользователь
Status = Active(иначеIDENTITY_USER_BLOCKED); для независимого режима — нет активного членства (INV-AM1; повтор — идемпотентный no-op). - В одном согласованном изменении: повысить пользователя (
Type Investor → Agent, зеркалоCompanyId = null— INV-2 user.md); создатьRoleAssignment (agent, companyId: null, Scope = Platform)(Platform-scope допустим дляagent— INV-R3). Зафиксировать факты:user-upserted (Type = Agent, CompanyId = null),user-access-changed. Membership-события в независимом режиме не публикуются (записи членства нет). - Система возвращает
{ UserId, UserType = Agent, Mode = Independent }. - Инвестор получает интерфейс агента (клиентская база — своя, лиды, реф-инструменты); может позже вступить в агентство по приглашению (UC-IDN-009) или вернуться к инвестору (become-investor).
Альтернативные Потоки
A1. Стать агентом по приглашению (вступить в агентство)
- Тело содержит
invitationId→ делегируется в accept-flow (UC-IDN-009, шаги 6–8): создаётсяActiveAgentMembership, рольagentвOrganization,Mode = JoinedAgency.
A2. Уже агент
- Шаг 3: активное членство →
IDENTITY_AGENT_ALREADY_IN_AGENCY(409); для независимого повтор — идемпотентный200(ужеAgentс рольюagentPlatform).
A3. Учётка заблокирована
- Шаг 3:
Status != Active→IDENTITY_USER_BLOCKED(400).
Постусловие
UserEntity.Type = Agent(независимый), зеркалоCompanyId = null;UserIdне изменился.- Создано
RoleAssignment (agent, Platform, Active). - Опубликованы
user-upsertedиuser-access-changed; membership-события отсутствуют (нет членства).
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
IDENTITY_AGENT_ALREADY_IN_AGENCY | 409 | Уже агент с активным членством (INV-AM1, A2) | «Вы уже агент агентства» |
IDENTITY_USER_BLOCKED | 400 | Status != Active (A3) | «Учётная запись заблокирована» |
IDENTITY_INVITATION_* | 403/404/409 | При join-режиме — как accept (A1) | см. UC-IDN-009 |
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
RoleAssignmentGrantedEvent | Выдача роли agent (Platform) | in-proc аудит |
AgentJoinedAgencyEvent | Только при join-режиме (A1) | in-proc |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
UserUpsertedKafkaEvent.v1 | pin.identity.users.v1 | Investor → Agent | crm, profile, messenger, notifications |
UserAccessChangedKafkaEvent.v1 | pin.identity.access.v1 | Выдача роли agent | crm, catalog, chessboard, gateway |
AgentMembershipChangedKafkaEvent.v1 | pin.identity.agent-membership-changed.v1 | Только при join-режиме (A1) | crm, rewards, notifications |
UserUpsertedKafkaEvent и UserAccessChangedKafkaEvent публикуются с ключом партиционирования UserId,
несут Version и применяются потребителями идемпотентно. Тип пользователя меняется в identity сразу (одно
согласованное изменение), но интерфейс агента (клиентская база, реф-инструменты) полностью доступен после
инвалидации кэшей прав по pin.identity.access.v1 — окно eventual-consistency ≤ TTL (UC-IDN-008); до этого
отдельные agent-функции могут краткое время отвечать 403. Повтор независимого become-agent идемпотентен
(A2): роль agent в Platform не дублируется. Гонка «независимый become-agent против accept приглашения
(A1)» сходится к одному членству — второй путь упирается в INV-AM1.
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Независимый become-agent выдаёт роль agent в scope Platform (INV-R3) | тест эффективных прав |
| AC2 | Тип Investor → Agent, зеркало CompanyId = null, UserId не меняется | тест до/после |
| AC3 | Membership-запись в независимом режиме не создаётся | тест отсутствия AgentMembership |
| AC4 | Повторный вызов независимого режима идемпотентен (A2) | двойной вызов → один результат |
| AC5 | С invitationId — вступление в агентство (A1, membership Active) | тест join-ветки |
| AC6 | Атомарность (user + assignment + события) | сбой между шагами → откат |
Обратные ссылки
Автоссылки: где используется этот документ.
- API (1): POST /api/v1/me/become-agent — Инвестор становится агентом
- index.md (1): Identity Service (Pin.Identity)
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- API метод: become-agent.md; реестр — ../api/README.md (агент-онбординг).
- Domain: ../domain/user.md (INV-2,
PromoteToAgent), ../domain/role.md (INV-R3, рольagentPlatform), ../domain/agent-membership.md. - События: ../events/user-upserted.md, ../events/user-access-changed.md, ../events/agent-membership-changed.md.
- Связанные UC: UC-IDN-009 (вступление по приглашению), UC-IDN-010 (выход из агентства).
- Решение: ADR-0061.