Согласование изменений
Владелец рабочего пространства может потребовать, чтобы административные изменения подтверждали участники, прежде чем они применятся. Это отдельный механизм от аппрувов транзакций: правило задаётся на действие, а не политикой, и подтверждают его владелец, администраторы или выбранная группа.
По умолчанию согласование выключено. Владелец включает его для каждого действия отдельно.
Какие действия
| Действие | Что покрывает | operation в запросе |
|---|---|---|
| Управление пользователями | приглашение пользователя или добавление существующего, создание API-пользователя, удаление пользователя и API-пользователя, смена роли, блокировка и разблокировка пользователя (уже заблокированного или уже разблокированного — ответ сразу, без запроса) | create, api_user_create, delete, role_change, block, unblock |
| Управление группами | создание, переименование и удаление группы, состав участников | create, update, delete, add_members, remove_members |
| Управление согласованием | порог администраторов и правила согласования, включая их выключение | threshold, action_setting |
| Добавление адреса в белый список | новый адрес во внешнем кошельке | нет |
| Управление тегами | создание, изменение и удаление тега, привязка и отвязка тегов к аккаунтам | create, update, delete, attach_detach |
| Переводы на one-time адреса | разрешение и запрет переводов по адресу вне белого списка | нет |
| Управление политиками | изменение правил политики и откат к версии из истории | update, rollback |
Согласование добавления адреса в белый список, тегов, переводов на one-time адреса и политик включается на стороне платформы: до включения правило для этих действий сохраняется, а изменение применяется сразу.
Правило одно на действие: нельзя требовать согласования удаления, но не
смены роли. Изменения администратора платформы (PLATFORM_ADMIN)
согласования не требуют. Поддержка платформы (PLATFORM_SUPPORT) не
приглашает и не удаляет пользователей рабочего пространства и не меняет им
роль: ответ 403 FORBIDDEN при любом
состоянии согласования.
Кто подтверждает
| Режим | Значение mode |
Кто подтверждает |
|---|---|---|
| Владелец | QUORUM_APPROVAL_MODE_OWNER_ONLY |
владелец рабочего пространства |
| Кворум администраторов | QUORUM_APPROVAL_MODE_ADMIN_QUORUM |
администраторы, включая неподписывающих (API-пользователи с этими ролями входят в состав, но подтвердить не могут); порог общий для всех действий в этом режиме |
| Группа | QUORUM_APPROVAL_MODE_SPECIFIC_GROUP |
участники выбранной группы, чьей роли доступно мобильное приложение (все роли, кроме наблюдателя и оператора; API-пользователи входят в состав, но подтвердить не могут), со своим порогом |
К кворуму администраторов или группе можно добавить обязательное подтверждение владельца.
Режим «Группа» требует включённого согласования действия «Управление группами»; само это действие в режиме «Группа» включается.
| Изменение правил и групп | Ответ |
|---|---|
| включить правило в режиме «Группа» или перевести на группу включённое, пока согласование действия «Управление группами» выключено | QUORUM_GROUP_MANAGEMENT_APPROVAL_DISABLED |
| выключить согласование действия «Управление группами», пока включено правило в режиме «Группа» | QUORUM_GROUP_MANAGEMENT_APPROVAL_IN_USE; в details.action_keys — ключи таких правил |
| удалить группу, выбранную в правиле (даже выключенном) | GROUP_REFERENCED_BY_QUORUM_SETTINGS; сначала переведите правило в другой режим |
PUT правила без mode, с group_uuid не в режиме «Группа» или без него в режиме «Группа», с group_threshold больше 1000 (без поля или 0 — порог 1) |
400 VALIDATION_ERROR |
PUT правила действия с fixed_mode («Управление согласованием») в другом режиме, с группой или с admins_can_cancel |
400 VALIDATION_ERROR |
PUT порога администраторов вне 1..1000 |
400 VALIDATION_ERROR |
группа из group_uuid не найдена |
GROUP_NOT_FOUND |
| группа из другого рабочего пространства | QUORUM_GROUP_NOT_IN_TENANT |
Подтверждение принимается только с привязанного устройства в мобильном приложении. Инициатор тоже подтверждает свой запрос, владелец в режиме «Владелец» подтверждает сам.
Право подтверждать теряют, когда:
- сменилась роль или человека убрали из группы подтверждающих;
- отвязано последнее устройство, например при смене телефона: роль, которой нужно устройство, неактивна до новой привязки;
- пользователь заблокирован в рабочем пространстве или на платформе;
- аккаунт ждёт настройки двухфакторной аутентификации.
Пользователь, которого блокируют, разблокируют, удаляют или переводят на другую роль, своё изменение не подтверждает: из состава запроса он исключён, и в списке ждущих голоса запрос у него не появляется. Если без него порог не набрать, порог опускается до числа оставшихся, а подтверждение владельца становится обязательным.
Заблокировать рабочее пространство нельзя
Правило, состав которого сейчас не набирает порог, включить нельзя: ответ
QUORUM_NO_ELIGIBLE_APPROVERS.
Порог администраторов не поднимается выше их числа, пока включено
правило в режиме кворума.
Выключение согласования действия «Управление согласованием» отменяет неподтверждённые запросы на изменение настроек.
| Согласование «Управления согласованием» | Настройки меняет владелец | Настройки меняет администратор, включая неподписывающего |
|---|---|---|
| выключено (по умолчанию) | применяется сразу | не принимается: QUORUM_CHANGE_REQUESTS_DISABLED |
| включено | запрос на согласование, владелец подтверждает сам | запрос на согласование, подтверждает владелец |
Как проходит согласование
- Инициатор выполняет действие как обычно. Если для него включено
согласование, изменение не применяется, а ответ приходит с кодом
QUORUM_APPROVAL_REQUIREDи идентификатором запроса. - Тем, кто подтверждает, приходит push-уведомление на привязанное устройство. Они подтверждают или отклоняют запрос в мобильном приложении.
- Когда подтверждений достаточно, изменение проверяется заново и применяется само. Повторять действие не нужно.
- Отказ, после которого порог уже не набрать, закрывает запрос. Пока запрос
ждёт голосов, его можно отменить. Запрос без нужных голосов
истекает (
EXPIRED).
Состав подтверждающих фиксируется при создании запроса и может только сокращаться. Кто потерял право подтверждать — выбывший — исключается из запроса при ближайшем голосе, повторной отправке того же изменения или другом изменении того же объекта (см. Одно изменение объекта за раз).
Выбывший больше не голосует по запросу, даже вернув право, а его голос не засчитывается; до исключения запрос ещё виден ему в списке ждущих голоса.
Запрос устаревает, если после создания у правила согласования изменились условие одобрения или порог либо после исключения выбывших порог уже не набрать. Устаревший запрос отменяется при первом голосе, повторной попытке того же изменения или другом изменении того же объекта; повторная попытка создаёт новый запрос.
Пока запрос не устарел и ждёт голосов, то же изменение от любого пользователя
рабочего пространства возвращает этот же запрос (тот же
approval_request_uuid). Инициатором остаётся создавший его: второй
отправитель может отменить запрос только как владелец или администратор
(см. Отмена).
| Статус | Значение |
|---|---|
PENDING |
ждёт подтверждений |
APPROVED |
подтверждений достаточно, изменение применяется |
EXECUTED |
изменение применено, в том числе когда применять уже нечего: правка тега, one-time адреса, блокировка заблокированного, разблокировка разблокированного — EXECUTED без изменений; привязка и отвязка тегов — FAILED, если не применилась ни одна операция |
FAILED |
подтверждено, но применить нельзя; причина — в execution_error: инициатор заблокирован, удалён или потерял права; пользователь уже в роли или группе; адрес уже в белом списке; рабочее пространство заморожено; тег отвязан; тег или политика изменились после запроса |
REJECTED |
отклонено |
EXPIRED |
72 часа прошли без нужных подтверждений |
CANCELLED |
отменено до решения: инициатором, владельцем или администратором, выключением согласования «Управления согласованием» (запросы на изменение настроек) или потому, что запрос устарел |
Ход согласования приходит событиями по WebSocket инициатору, подтверждающим и
держателям VIEW_CHANGE_APPROVALS; на привязанное устройство — только
инициатору и подтверждающим. Статус и голоса — в карточке запроса. Каждое
решение попадает в журнал аудита.
Просмотр изменений в мобильном приложении
Небольшое изменение — например, новый адрес в белом списке или правка нескольких полей одного правила политики без перемещения — показано прямо в карточке запроса в мобильном приложении: что было и что станет.
Если изменение большое, карточка показывает краткую сводку и кнопку «Посмотреть изменения». Кнопка открывает экран изменений с полным описанием «было → стало», добавленными и удалёнными значениями и пояснениями.
Подтверждают и отклоняют запрос в карточке: с экрана изменений вернитесь назад. У большого изменения кнопка «Подтвердить» доступна только после того, как экран изменений показал описание, — до этого под сроком запроса стоит подсказка «Чтобы подтвердить, откройте изменения»; отклонить можно сразу.
| Состояние | Что показано |
|---|---|
| Описание изменения ещё не загрузилось | сводка и кнопка «Посмотреть изменения»; «Подтвердить» — как у большого изменения |
| Описание изменения требует новой версии мобильного приложения | сводка, плашка «Обновите приложение, чтобы увидеть изменение целиком» и основные данные изменения; кнопки просмотра нет, «Подтвердить» доступна сразу |
| В изменении есть ослабление контроля (например, снятие запрета) или предупреждение | плашка в карточке: у большого изменения «Есть важные изменения: откройте их перед решением», у небольшого — «Есть важные изменения: проверьте их перед решением» над ним |
| Описания изменения нет | основные данные изменения в карточке, без «было → стало» |
| Описание изменения урезано | плашка «Показано не всё: описание урезано по размеру» на экране изменений |
| При открытии экрана изменений запрос уже не ждёт подтверждений или не найден | плашка «Запрос больше не ожидает решения»; описание, если оно есть, показано |
Одно изменение объекта за раз
Пока по объекту есть запрос в работе — он ждёт подтверждений или подтверждён
и применяется (не дольше 15 минут после подтверждения), — другое изменение
того же объекта не принимается: ответ
CHANGE_REQUEST_IN_PROGRESS
с данными этого запроса.
| Объект | Что считается его изменением |
|---|---|
| политика транзакций | обновление правил и откат к версии из истории |
| правило согласования действия | изменение правила |
| порог кворума администраторов | изменение порога |
| пользователь | смена роли, блокировка, разблокировка, удаление |
| группа | изменение, удаление, добавление и удаление участников |
| тег | изменение и удаление; привязка и отвязка тегов объект не держат |
| one-time адреса рабочего пространства | разрешение и запрет |
| Изменение объекта, пока по нему есть запрос в работе | Ответ |
|---|---|
| другое изменение — и требующее согласования, и не требующее его сейчас | CHANGE_REQUEST_IN_PROGRESS |
| другое изменение, если запрос в работе устарел или его срок вышел | запрос закрывается (CANCELLED или EXPIRED), изменение идёт как при свободном объекте: запросом или сразу |
| то же самое изменение, пока согласование включено и запрос ждёт подтверждений | как при регистрации: QUORUM_APPROVAL_REQUIRED с этим запросом |
изменение администратора платформы (PLATFORM_ADMIN) |
принимается, запрос остаётся в работе |
Чтобы изменить объект по-другому, запрос в работе нужно закрыть: дождаться решения или срока либо отменить его.
Отмена
| Кто | Что может отменить |
|---|---|
| инициатор | свой запрос |
| владелец, пока сам вправе подтверждать | любой запрос рабочего пространства, который ждёт подтверждений |
| администраторы, включая неподписывающих, пока сами вправе подтверждать | запрос действия, в правиле которого включено admins_can_cancel |
admins_can_cancel по умолчанию выключено; у правила «Управление
согласованием» его включить нельзя. Запрос о себе самом — о своей
блокировке, удалении или смене роли — администратор отменить не может.
Подтверждения с устройства отмена не требует.
Для интеграции
Ответ на действие, которому нужно согласование (текст error у каждого
действия свой, опознавайте ответ по code):
{
"error": "…",
"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"
}
}
Ответ на изменение объекта, по которому есть запрос в работе:
{
"error": "another change request for this object is in progress",
"code": "CHANGE_REQUEST_IN_PROGRESS",
"details": {
"approval_request_uuid": "3f0c5c1e-…",
"action": "<action_key>",
"operation": "update",
"status": "PENDING",
"initiator_uuid": "7d1e…",
"initiator": { "user_uuid": "7d1e…", "user_type": "user", "email": "anna@example.com", "first_name": "Anna" },
"initiated_by_me": false,
"can_cancel": true,
"created_at": "2026-10-05T10:00:00Z",
"expires_at": "2026-10-08T10:00:00Z"
}
}
Поле details |
Когда есть |
|---|---|
approval_request_uuid, action, status, initiated_by_me, created_at |
всегда |
operation |
у действий с operation в таблице «Какие действия» |
expires_at |
у запроса в PENDING |
initiator_uuid, initiator |
только если вызывающему доступна карточка запроса — он участник запроса или у него VIEW_CHANGE_APPROVALS; initiator может отсутствовать и у них |
can_cancel |
может отсутствовать |
Ключ действия отдаёт GET …/quorum/actions полем key, в ответе на
действие он приходит полем action. Режим везде передаётся значением из
таблицы режимов: в ответе на действие, в запросах согласования и в
настройках.
| Ответ действия | Когда |
|---|---|
403 QUORUM_APPROVAL_REQUIRED |
правило включено; запрос зарегистрирован, его данные — в details |
409 QUORUM_NO_ELIGIBLE_APPROVERS |
правило включено, но подтверждать сейчас некому; изменение не сохраняется |
409 CHANGE_REQUEST_IN_PROGRESS |
по объекту изменения уже есть запрос в работе, см. Одно изменение объекта за раз; изменение не сохраняется, в details — этот запрос |
409 CHANGE_REQUEST_ALREADY_FINALIZED |
такой же запрос только что закрылся; изменение не сохраняется, повторите действие. У управления политиками код в теле — INTERNAL_ERROR при том же статусе |
503 CHANGE_APPROVAL_UNAVAILABLE |
проверить, нужно ли согласование, сейчас нельзя; изменение не сохраняется, повторите позже. У белого списка, тегов и one-time адресов тот же сбой может прийти как 500 INTERNAL_ERROR |
| Метод | Путь | Право |
|---|---|---|
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 |
подтверждающий этого запроса (входит в sets[]), с привязанного устройства |
POST |
…/change-approvals/{request_uuid}/cancel |
инициатор запроса, владелец и, если правило действия это разрешает (admins_can_cancel), администраторы, см. Отмена; с привязанного устройства — пользователь, привязавший его к этому рабочему пространству |
GET |
/api/v2/admin/tenants/{tenant_uuid}/quorum |
VIEW_QUORUM_SETTINGS |
GET |
…/quorum/actions — список действий: ключ key (он же {action_key} в пути PUT) и fixed_mode — режим, заданный платформой; QUORUM_APPROVAL_MODE_UNSPECIFIED — режим выбирает владелец |
VIEW_QUORUM_SETTINGS |
PUT |
…/quorum/actions/{action_key}, …/quorum/threshold |
MANAGE_QUORUM_SETTINGS; PROPOSE_QUORUM_SETTINGS — только запросом, пока включено «Управление согласованием» |
GET |
…/quorum/gated-actions — действия рабочего пространства и requires_approval: потребует ли действие у вызывающего согласования; права на настройки не нужны |
пользователь или API-пользователь с активной ролью в рабочем пространстве; с устройства — 403 |
Параметры списков: scope (по умолчанию PENDING_MY_VOTE), statuses
(можно несколько: PENDING, APPROVED, REJECTED, EXPIRED, EXECUTED,
FAILED, CANCELLED), action, limit (по умолчанию 50, не больше 200),
offset; сортировка — новые первыми.
Устройству, привязанному в нескольких рабочих пространствах, хватает одного
запроса: у каждого элемента списка своё пространство в tenant_uuid и
tenant_name, а initiated_by_me считается по пользователю, привязавшему
устройство к этому пространству. Карточка и голос — по путям пространства
элемента.
Голос подписывается ключом привязанного устройства, который открывается
биометрией или ключом безопасности. В теле approve и reject приложение
передаёт signed_vote — JSON голоса в base64 — и signature, подпись
Ed25519 этих байтов, тоже в base64:
{"type": "change_vote", "tenant_uuid": "…", "request_uuid": "3f0c5c1e-…",
"vote": "approve", "device_uuid": "…", "changes_hash": "9f2c…"}
changes_hash — отпечаток описания из карточки, по которой голосуют, пусто
без описания. Голос approve засчитывается, только если он совпадает с
changes_hash запроса; reject — при любом.
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[] и неподанные голоса,
поданные голоса остаются; кто и почему отменил — в cancelled_by и
cancel_reason.
POST /api/v2/admin/tenants/{tenant_uuid}/change-approvals/3f0c5c1e-…/cancel
{
"request": {
"request_uuid": "3f0c5c1e-…",
"status": "CANCELLED",
"cancel_reason": "owner",
"cancelled_by": { "user_uuid": "5b2f…", "user_type": "user", "email": "boss@example.com" },
"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_SIGNATURE_REQUIRED |
approve, reject |
голос не подписан ключом устройства: версия приложения устарела, его нужно обновить |
CHANGE_VOTE_SIGNATURE_INVALID |
approve, reject |
голос подписан не ключом этого устройства или подписан другой голос; у approve — и подписанный changes_hash не совпадает с запросом |
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,
"can_cancel": true,
"cancel_reason": "",
"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",
"execution_error": "",
"expires_at": "2026-10-08T10:00:00Z",
"created_at": "2026-10-05T10:00:00Z",
"updated_at": "2026-10-05T10:00:00Z",
"summary": {},
"changes_hash": "",
"entities": {},
"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 |
этот участник — вы; с привязанного устройства — пользователь, привязавший его к этому рабочему пространству |
can_cancel |
вы можете отменить запрос сейчас, см. Отмена; есть и в списке |
cancel_reason |
почему запрос в CANCELLED: initiator, owner, admin — отменил человек; rule_changed, approvers_changed — запрос устарел; change_requests_disabled — выключено «Управление согласованием». Пусто у запросов, отменённых раньше, чем появилось поле |
cancelled_by |
кто отменил, участник, как initiator; поля нет, если запрос устарел или отменён раньше, чем появилось поле |
execution_error |
причина отказа при FAILED; у остальных статусов — пустая строка |
expires_at, created_at, updated_at |
срок, время создания и последнего изменения запроса, RFC 3339 |
payload |
что будет изменено |
display |
названия и имена того, на что ссылается payload, на момент создания запроса; поля нет, если снимка нет |
summary |
однострочная сводка изменения по локалям шаблона, en есть всегда; есть и в списке; пусто, если описания изменения нет |
changes |
описание изменения «было → стало» на момент создания запроса; в карточке и ответах на голос и отмену; поля нет, если описания нет |
changes_hash |
отпечаток changes: меняется вместе с ним; клиент его не вычисляет, а только сравнивает; есть и в списке, пусто без описания |
entities |
пользователи, сервисные аккаунты и группы из changes — свежие на момент ответа: ключ "<kind>:<id>" (kind — user, service_account, user_group), значение — name, subtitle (email), subtype (user или api_user), is_me, missing; там же, где changes, иначе пустой объект |
| Действие | operation |
Поля display |
|---|---|---|
| Управление пользователями | role_change, delete, block, unblock |
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 читается как есть.
Правило действия задаёт PUT …/quorum/actions/{action_key}; ответ —
сохранённое правило с составом подтверждающих. group_threshold без поля —
1; admins_can_cancel без поля сохраняет прежнее значение.
PUT /api/v2/admin/tenants/{tenant_uuid}/quorum/actions/{action_key}
{
"is_enabled": true,
"mode": "QUORUM_APPROVAL_MODE_SPECIFIC_GROUP",
"group_uuid": "33f1…",
"group_threshold": 2,
"owner_approval": false,
"admins_can_cancel": false
}
{
"setting": {
"action_key": "<action_key>",
"display_name": "Manage users",
"is_enabled": true,
"mode": "QUORUM_APPROVAL_MODE_SPECIFIC_GROUP",
"group_uuid": "33f1…",
"group_threshold": 2,
"owner_approval": false,
"admins_can_cancel": false,
"approver_sets": [ { "external_id": "A", "threshold": 2, "user_uuids": [ "5b2f…", "7d1e…", "9a4c…" ] } ],
"reachable": true
}
}
Порог кворума администраторов задаёт PUT …/quorum/threshold с телом
{"admin_quorum_threshold": 2} (1..1000); ответ — настройки целиком, как у
GET …/quorum.
Состав подтверждающих в ответе настроек:
| Поле | Что содержит |
|---|---|
approver_sets, reachable у правила |
кто подтверждал бы сейчас, без учёта того, над кем изменение, и набирает ли состав порог; есть и у выключенных правил |
owner_approvers, admin_quorum_approvers |
кто прямо сейчас подтверждает как владелец и как администратор, независимо от правил |
active_member_uuids у группы в списке групп |
участники, которые считаются в режиме «Группа» |
Состав ещё не сохранённого правила считается из этих пулов, участников группы и порога.
Описание изменения
changes описывает изменение так, что его можно показать, не зная схемы
payload действия: секции (например, правила политики) с полями «было» и
«стало».
| Действие | changes |
|---|---|
управление политиками (update, rollback), управление тегами (все операции), добавление адреса в белый список, one-time адреса |
есть; если описание не собралось — как у остальных действий |
| остальные действия | поля changes нет, summary и changes_hash пусты; показывайте запрос по display |
Пример — фрагмент карточки с описанием изменения правила политики:
выросла сумма и изменился набор аппруверов A (остальные поля карточки, поля
со значениями по умолчанию и часть шаблонов texts опущены):
{
"request": {
"request_uuid": "3f0c5c1e-…",
"changes": {
"schema_version": 1,
"summary": { "code": "policy.summary.update.transfer", "args": { "rules": "1" } },
"sections": [
{
"id": "5b0d2f1c-…", "kind": "policy.rule", "op": "CHANGE_VIEW_OP_MODIFIED",
"title": { "code": "policy.rule.title", "args": { "name": "Large transfers" } },
"position_before": 3, "position_after": 1,
"fields": [
{ "id": "amount", "label": { "code": "policy.field.amount" }, "op": "CHANGE_VIEW_OP_MODIFIED",
"before": { "money": { "amount": "1000", "currency": "USD" } },
"after": { "money": { "amount": "5000", "currency": "USD" } } },
{ "id": "approval.sets.A.members", "op": "CHANGE_VIEW_OP_MODIFIED",
"label": { "code": "policy.field.approval.set.members", "args": { "set": "A" } },
"after": { "set": { "items": [
{ "op": "CHANGE_VIEW_OP_ADDED", "value": { "entity": { "kind": "user_group", "id": "33f1…" } } }
] } } },
{ "id": "approval.sets.A.threshold", "op": "CHANGE_VIEW_OP_MODIFIED", "important": true,
"label": { "code": "policy.field.approval.set.threshold", "args": { "set": "A" } },
"before": { "number": "2" }, "after": { "number": "1" } }
]
}
],
"notes": [ { "severity": "CHANGE_VIEW_NOTE_SEVERITY_INFO", "text": { "code": "policy.note.order.transfer" } } ],
"texts": {
"policy.summary.update.transfer": { "by_locale": { "en": "Transfers policy: {rules} rule(s) affected", "ru": "Политика переводов: затронуто правил — {rules}" } },
"policy.field.amount": { "by_locale": { "en": "Amount from", "ru": "Сумма от" } }
}
},
"summary": { "en": "Transfers policy: 1 rule(s) affected", "ru": "Политика переводов: затронуто правил — 1" },
"changes_hash": "9f2c…",
"entities": { "user_group:33f1…": { "name": "Finance" } }
}
}
Поле changes |
Что содержит |
|---|---|
summary |
однострочная сводка — текст |
fields |
поля верхнего уровня — у простых изменений (порог 2 → 3) |
sections |
секции — у составных изменений, в порядке показа; у секции id, kind (подсказка для особого показа, например policy.rule), title, op, позиции, fields, вложенные sections, notes и hidden_unchanged — сколько неизменённых полей и секций не перечислено |
notes |
пояснения и предупреждения: severity — CHANGE_VIEW_NOTE_SEVERITY_INFO или CHANGE_VIEW_NOTE_SEVERITY_WARNING; пустое значение и CHANGE_VIEW_NOTE_SEVERITY_UNSPECIFIED показываются как CHANGE_VIEW_NOTE_SEVERITY_INFO, незнакомое — как CHANGE_VIEW_NOTE_SEVERITY_WARNING; text |
truncated |
описание урезано по размеру и показывает не всё |
texts |
шаблоны текстов: код → by_locale → шаблон |
schema_version |
сейчас 1; у описания старшей версии показывается сводка, а само изменение — по display, как у запроса без описания |
Текст приходит кодом и аргументами (code, args). Шаблон кода берётся из
словаря клиента, иначе из texts на локали клиента, иначе на en, иначе
показывается сам код.
Подстановка — имя аргумента в фигурных скобках: строчные латинские буквы,
цифры и _. Она делается за один проход: {set} заменяется на args.set,
{…} внутри значения аргумента не раскрывается, подстановка без аргумента и
прочие скобки остаются как есть.
Значение аргумента — готовая строка, часто текст пользователя, например
название правила. Показывайте каждое значение изолированно от соседнего
текста, как элемент bdi в HTML: символы направления текста в нём не должны
переставлять остальную строку.
op |
Секция | Поле |
|---|---|---|
CHANGE_VIEW_OP_ADDED |
добавлена; position_before — 0 |
значение в after |
CHANGE_VIEW_OP_REMOVED |
удалена; position_after — 0 |
значение в before |
CHANGE_VIEW_OP_MODIFIED |
изменена; позиции могут различаться | before и after; у множества — after с элементами, отмеченными CHANGE_VIEW_OP_ADDED и CHANGE_VIEW_OP_REMOVED |
CHANGE_VIEW_OP_MOVED |
изменилась только позиция | — |
CHANGE_VIEW_OP_UNCHANGED |
без изменений, показана для контекста | значение в after |
important у поля — изменение ослабляет контроль: снятие запрета, снижение
порога.
| Вид значения | Что содержит |
|---|---|
text |
текст кодом и аргументами |
literal |
строка как есть: название, адрес |
flag, number |
да или нет; десятичное число |
money |
amount в валюте currency (USD) или, без currency, в единицах актива asset; при minor_units — в минимальных единицах актива |
entity |
сущность kind и id: имя — из entities карточки, если ключ там есть и не missing, иначе name снимка, а без него — id; subtitle — уточнение; missing — сущности нет; виды — в таблице ниже |
enum_value |
label для показа, code — значение |
set |
элементы items с value и op: в изменённом множестве — CHANGE_VIEW_OP_ADDED или CHANGE_VIEW_OP_REMOVED, элемент как есть — CHANGE_VIEW_OP_UNSPECIFIED или CHANGE_VIEW_OP_UNCHANGED; omitted — сколько элементов не перечислено |
list |
упорядоченный список items; omitted |
time, unset |
время; значение не задано |
| незнакомый вид | показывается строка fallback |
kind сущности |
Что это | Имя |
|---|---|---|
user, service_account, user_group |
пользователь, сервисный аккаунт, группа | свежее, в entities карточки |
vault |
аккаунт | снимок в name на момент создания запроса |
tag |
тег | снимок в name |
whitelisted_wallet |
внешний (whitelisted) кошелёк | снимок в name; subtitle — тип: internal, external, contract |
asset |
актив | снимок: name — символ, subtitle — сеть |