Перейти к содержанию

Согласование изменений

Владелец рабочего пространства может потребовать, чтобы административные изменения подтверждали участники, прежде чем они применятся. Это отдельный механизм от аппрувов транзакций: правило задаётся на действие, а не политикой, и подтверждают его владелец, администраторы или выбранная группа.

По умолчанию согласование выключено. Владелец включает его для каждого действия отдельно.

Какие действия

Действие Что покрывает operation в запросе
Управление пользователями приглашение пользователя, создание API-пользователя, удаление пользователя и API-пользователя, смена роли create, api_user_create, delete, role_change
Управление группами создание, переименование и удаление группы, состав участников create, update, delete, add_members, remove_members
Управление согласованием порог администраторов и правила согласования, включая их выключение threshold, action_setting
Добавление адреса в белый список новый адрес во внешнем кошельке нет
Управление тегами создание, изменение и удаление тега, привязка и отвязка тегов к аккаунтам create, update, delete, attach_detach
Переводы на one-time адреса разрешение и запрет переводов по адресу вне белого списка нет
Управление политиками изменение правил политики и откат к версии из истории update, rollback

Правило одно на действие: нельзя требовать согласования удаления, но не смены роли. Изменения администратора платформы (PLATFORM_ADMIN) согласования не требуют; у поддержки платформы (PLATFORM_SUPPORT) такого обхода нет.

Кто подтверждает

Режим Значение mode Кто подтверждает
Владелец QUORUM_APPROVAL_MODE_OWNER_ONLY владелец рабочего пространства
Кворум администраторов QUORUM_APPROVAL_MODE_ADMIN_QUORUM администраторы, включая неподписывающих; порог общий для всех действий в этом режиме
Группа QUORUM_APPROVAL_MODE_SPECIFIC_GROUP участники выбранной группы, чьей роли доступно мобильное приложение (все роли, кроме наблюдателя и оператора), со своим порогом

К кворуму администраторов или группе можно добавить обязательное подтверждение владельца.

Режим «Группа» требует включённого согласования действия «Управление группами»; само это действие в режиме «Группа» включается.

Изменение правил Ответ
включить правило в режиме «Группа» или перевести на группу включённое, пока согласование действия «Управление группами» выключено QUORUM_GROUP_MANAGEMENT_APPROVAL_DISABLED
выключить согласование действия «Управление группами», пока включено правило в режиме «Группа» QUORUM_GROUP_MANAGEMENT_APPROVAL_IN_USE; в details.action_keys — ключи таких правил

Подтверждение принимается только с привязанного устройства в мобильном приложении — это второй фактор. Инициатор тоже подтверждает свой запрос, владелец в режиме «Владелец» подтверждает сам.

Заблокировать рабочее пространство нельзя

Правила согласования подтверждает только владелец. Правило, состав которого сейчас не набирает порог, включить нельзя: ответ QUORUM_NO_ELIGIBLE_APPROVERS. Порог администраторов не поднимается выше их числа, пока включено правило в режиме кворума.

Настройки согласования меняет владелец. Администраторы, включая неподписывающих, могут предлагать изменения, пока владелец держит включённым согласование действия «Управление согласованием». Выключение этого согласования отменяет неподтверждённые запросы на изменение настроек.

«Управление согласованием» Изменение владельца Изменение администратора
выключено (по умолчанию) применяется сразу не принимается: QUORUM_CHANGE_REQUESTS_DISABLED
включено запрос на согласование, владелец подтверждает сам запрос на согласование, подтверждает владелец

Как проходит согласование

.........Pert......... ............ИнициаторИнициаторPertPertУчастники согласования(мобильное приложение)Участники согласования(мобильное приложение)действие, например удаление пользователя403 QUORUM_APPROVAL_REQUIRED+ идентификатор запросаpush-уведомление,запрос в очереди согласованияподтвердить или отклонить(с привязанного устройства)alt[подтверждений достаточно]изменение применяетсяEXECUTED, либо FAILED с причиной[отказ, после которого порог не набрать]REJECTED[срок вышел]EXPIRED[инициатор отменил запрос]отменаCANCELLED
.........Pert......... ............ИнициаторИнициаторPertPertУчастники согласования(мобильное приложение)Участники согласования(мобильное приложение)действие, например удаление пользователя403 QUORUM_APPROVAL_REQUIRED+ идентификатор запросаpush-уведомление,запрос в очереди согласованияподтвердить или отклонить(с привязанного устройства)alt[подтверждений достаточно]изменение применяетсяEXECUTED, либо FAILED с причиной[отказ, после которого порог не набрать]REJECTED[срок вышел]EXPIRED[инициатор отменил запрос]отменаCANCELLED
  1. Инициатор выполняет действие как обычно. Если для него включено согласование, изменение не применяется, а ответ приходит с кодом QUORUM_APPROVAL_REQUIRED и идентификатором запроса.
  2. Тем, кто подтверждает, приходит push-уведомление на привязанное устройство. Они подтверждают или отклоняют запрос в мобильном приложении.
  3. Когда подтверждений достаточно, изменение проверяется заново и применяется само. Повторять действие не нужно.
  4. Отказ, после которого порог уже не набрать, закрывает запрос. Пока запрос ждёт голосов, инициатор может его отменить. Запрос без нужных голосов истекает через 72 часа.

Права инициатора при применении перепроверяются у изменений пользователей, групп и настроек согласования. Адрес белого списка, тег, переводы на one-time адреса и политика применяются и тогда, когда инициатор к этому моменту потерял права.

Повторная попытка того же изменения, пока запрос ждёт голосов, новый запрос не создаёт.

Статус Значение
PENDING ждёт подтверждений
APPROVED подтверждений достаточно, изменение применяется
EXECUTED изменение применено. Если применять к этому моменту уже нечего, правка тега, привязка уже привязанного тега и настройка one-time адресов тоже закрываются EXECUTED — без изменений
FAILED подтверждено, но применить нельзя; причина — в карточке запроса. Например: пользователь уже в этой роли или уже в группе, адрес уже в белом списке, тег уже отвязан, политика изменилась после запроса
REJECTED отклонено
EXPIRED 72 часа прошли без нужных подтверждений
CANCELLED отменено до решения

Ход согласования приходит событиями по WebSocket участникам согласования: инициатору, тем, кто подтверждает, и тем, кто видит все запросы рабочего пространства. На привязанное устройство события приходят только инициатору и тем, кто подтверждает. Каждое решение попадает в журнал аудита.

Для интеграции

Ответ на действие, которому нужно согласование:

{
  "error": "change requires quorum approval",
  "code": "QUORUM_APPROVAL_REQUIRED",
  "details": {
    "approval_request_uuid": "3f0c5c1e-…",
    "action": "<action_key>",
    "operation": "delete",
    "mode": "QUORUM_APPROVAL_MODE_OWNER_ONLY",
    "expires_at": "2026-09-17T10:00:00Z"
  }
}

Ключ действия отдаёт GET …/quorum/actions полем key, в ответе на действие он приходит полем action. Режим везде передаётся значением из таблицы режимов: в ответе на действие, в запросах согласования и в настройках.

Ответ действия Когда
403 QUORUM_APPROVAL_REQUIRED правило включено; запрос зарегистрирован, его данные — в details
409 правило включено, но подтверждать сейчас некому; изменение не сохраняется. У управления пользователями, группами и согласованием код ошибки — QUORUM_NO_ELIGIBLE_APPROVERS
503 CHANGE_APPROVAL_UNAVAILABLE согласование временно недоступно; изменение не сохраняется, повторите позже
Метод Путь Право
GET /api/v2/admin/tenants/{tenant_uuid}/change-approvals?scope=PENDING_MY_VOTE\|INITIATED_BY_ME\|ALL пользователь рабочего пространства; ALL — VIEW_CHANGE_APPROVALS; с привязанного устройства — только PENDING_MY_VOTE и INITIATED_BY_ME пользователя, привязавшего устройство к этому рабочему пространству
GET /api/v2/admin/change-approvals?scope=… — тот же список сразу по всем рабочим пространствам, доступным токену; страница общая как у списка рабочего пространства, по каждому пространству отдельно; ALL — только пространства с VIEW_CHANGE_APPROVALS, ни одного — 403
GET …/change-approvals/{request_uuid} участник запроса или VIEW_CHANGE_APPROVALS; с устройства — участник
POST …/change-approvals/{request_uuid}/approve, …/reject участник согласования, с привязанного устройства
POST …/change-approvals/{request_uuid}/cancel инициатор запроса; с привязанного устройства — если роль инициатора требует привязки устройства
GET /api/v2/admin/tenants/{tenant_uuid}/quorum VIEW_QUORUM_SETTINGS
GET …/quorum/actions — список действий: ключ key (он же {action_key} в пути PUT) и fixed_mode у действия, режим которого не выбирается VIEW_QUORUM_SETTINGS
PUT …/quorum/actions/{action_key}, …/quorum/threshold MANAGE_QUORUM_SETTINGS; PROPOSE_QUORUM_SETTINGS — только запросом, пока включено «Управление согласованием»
GET …/quorum/gated-actions — действия рабочего пространства и requires_approval: потребует ли действие у вызывающего согласования; предупреждение до отправки изменения, прав на настройки не требует пользователь или API-пользователь с активной ролью в рабочем пространстве; с устройства — 403

Устройству, привязанному в нескольких рабочих пространствах, хватает одного запроса: у каждого элемента списка своё пространство в tenant_uuid и tenant_name, а initiated_by_me считается по пользователю, привязавшему устройство к этому пространству. Карточка и голос — по путям пространства элемента.

GET /api/v2/admin/change-approvals?scope=PENDING_MY_VOTE&limit=50
{
  "items": [
    { "request_uuid": "3f0c5c1e-…", "tenant_uuid": "0f8b7a5e-…", "tenant_name": "Treasury",
      "action": "<action_key>", "action_display_name": "Manage users", "status": "PENDING", "initiated_by_me": false },
    { "request_uuid": "a91d07b2-…", "tenant_uuid": "7c2e41d9-…", "tenant_name": "Trading",
      "action": "<action_key>", "action_display_name": "Manage tags", "status": "PENDING", "initiated_by_me": true }
  ],
  "total": 2,
  "limit": 50,
  "offset": 0
}

Ответ на принятый голос и на отмену — обновлённая карточка запроса. Отмена переводит в CANCELLED запрос, открытые sets[] и неподанные голоса, поданные голоса остаются.

POST /api/v2/admin/tenants/{tenant_uuid}/change-approvals/3f0c5c1e-…/cancel
{
  "request": {
    "request_uuid": "3f0c5c1e-…",
    "status": "CANCELLED",
    "sets": [
      { "external_id": "OWNER", "threshold": 1, "status": "CANCELLED",
        "approvers": [ { "user_uuid": "5b2f…", "status": "CANCELLED", "is_me": false } ] }
    ]
  }
}
Ответ На что Когда
CHANGE_VOTE_DEVICE_REQUIRED approve, reject голос не с привязанного устройства
CHANGE_VOTE_NOT_ALLOWED approve, reject голосующий не входит в состав подтверждающих запроса
CHANGE_VOTE_CONFLICT approve, reject противоположный голос уже подан; повтор того же голоса ничего не меняет
CHANGE_REQUEST_CANCEL_NOT_ALLOWED cancel отменяет не инициатор запроса
200, та же карточка cancel запрос уже отменён
CHANGE_REQUEST_ALREADY_FINALIZED все три запрос уже закрыт или истёк
CHANGE_REQUEST_NOT_FOUND все три запроса нет в рабочем пространстве; то же у карточки

Карточка запроса несёт всё, что нужно показать человеку, без дополнительных запросов:

{
  "request": {
    "request_uuid": "3f0c5c1e-…",
    "tenant_uuid": "…",
    "tenant_name": "Treasury",
    "action": "<action_key>",
    "action_display_name": "Manage users",
    "initiator": { "user_uuid": "7d1e…", "user_type": "user", "email": "owner@example.com", "first_name": "Anna", "last_name": "Smirnova", "api_user_name": "" },
    "initiated_by_me": false,
    "payload": { "operation": "delete", "tenant_uuid": "…", "user_uuid": "9a4c…" },
    "display": { "user": { "user_uuid": "9a4c…", "user_type": "user", "email": "ivan@example.com", "first_name": "Ivan" }, "current_role": "EDITOR" },
    "status": "PENDING",
    "sets": [
      {
        "external_id": "OWNER",
        "threshold": 1,
        "status": "PENDING",
        "approvers": [ { "user_uuid": "5b2f…", "status": "PENDING", "user": { "user_uuid": "5b2f…", "user_type": "user", "email": "boss@example.com", "first_name": "", "last_name": "", "api_user_name": "" }, "is_me": true } ]
      }
    ]
  }
}
Поле Что содержит
initiator, sets[].approvers[].user участник: user_type — user или api_user; у пользователя email, first_name, last_name, у API-пользователя api_user_name
initiated_by_me, sets[].approvers[].is_me этот участник — вы; с привязанного устройства — пользователь, привязавший его к этому рабочему пространству
payload что будет изменено
display названия и имена того, на что ссылается payload, на момент создания запроса; поля нет, если снимка нет
Действие operation Поля display
Управление пользователями role_change, delete user, current_role
Управление группами update, delete group_name
add_members, remove_members group_name, users — по порядку payload; у длинного списка только первые участники
Управление согласованием action_setting action_display_name, group_name
Добавление адреса в белый список нет whitelisted_wallet_name, currency_symbol, currency_descriptor, blockchain_name
Управление тегами update, delete tag
attach_detach vaults, tags
Управление политиками update policy_type_name
rollback policy_type_name, version_published_at

Остальные операции приходят без display: их payload читается как есть.

Состав подтверждающих в ответе настроек:

Поле Что содержит
approver_sets, reachable у правила кто подтверждал бы сейчас и набирает ли состав порог; есть и у выключенных правил
owner_approvers, admin_quorum_approvers кто прямо сейчас подтверждает как владелец и как администратор, независимо от правил
active_member_uuids у группы в списке групп участники, которые считаются в режиме «Группа»

Состав ещё не сохранённого правила считается из этих пулов, участников группы и порога.