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

Рецепт: одобрения human-in-the-loop

Цель: «применение деплоя (либо запуск оркестрации, либо открытие голосовой сессии) не происходит, пока его не одобрит человек, который не является запросившим — и это решение становится зафиксированным фактом».

Движок одобрений работает в стандартном бинарнике; модель governance объясняет позицию. Этот рецепт — операционная обвязка.

Действия модулей, которые изменили бы инфраструктуру, проходят через мост human-in-the-loop. Он включается конфигурацией — без него эти действия остаются deny-closed:

Окно терминала
OLIVARES_APPROVAL_BRIDGE_CONFIG=/etc/olivares/approval-bridge.json

Запускайте компонент, который открывает одобрения, под его собственной служебной учётной записью, которой никогда нет в пуле одобряющих. Разделение обязанностей обеспечивается на стороне движка (открывший не может решить собственный запрос, а системный токен вообще не может одобрять) — если учётная запись открывшего одновременно является одобряющей, вы построили liveness-deadlock, а не контроль.

Окно терминала
curl -ks -X POST "$BASE/v1/m/governance/approvals" \
-H "Authorization: Bearer $TOKEN" -H "X-Olivares-Tenant: $TENANT" \
-H 'Content-Type: application/json' \
-d '{
"subject_kind": "deployment",
"subject_ref": "deploy:payments-api",
"action": "deploy.apply",
"reason": "rollout v2.4.1",
"expires_in_seconds": 3600
}'

Запрос открывается deny-closed и с ограничением по времени, привязанный к точному плану, который он покрывает. Если включённая политика одобрений совпадает с (action, subject_kind), авторитетным является required_approvals этой политики — запросивший не может понизить планку со стороны запроса.

Окно терминала
# The queue (filter by status / action):
curl -ks "$BASE/v1/m/governance/approvals?status=pending" \
-H "Authorization: Bearer $APPROVER_TOKEN" -H "X-Olivares-Tenant: $TENANT"
# The decision (approval-admin permission):
curl -ks -X POST "$BASE/v1/m/governance/approvals/$ID/decisions" \
-H "Authorization: Bearer $APPROVER_TOKEN" -H "X-Olivares-Tenant: $TENANT" \
-d '{"decision":"approve","note":"reviewed the plan hash"}'

Что движок обеспечивает на стороне сервера — ничто из этого не является клиентским соглашением:

  • Разделение обязанностей: принимающий решение определяется по стабильному идентификатору пользователя; запросивший не может решать, и один и тот же человек не может решить дважды (уникальный индекс, а не правило UI).
  • Истечение срока: истёкший запрос никогда не сможет получить обязывающее решение, даже до того как sweeper материализует состояние.
  • Порог по уровню риска: действия, предклассифицированные как CRITICAL (семейство kill-switch, финализация credential и им подобные) требуют как минимум двух разных людей-одобряющих с сильной (AAL3) аутентификацией на каждое решение — и этот порог структурен: политика одобрений, пытающаяся понизить уровень, заново получает порог в точке решения.

Каждое решение добавляется в audit ledger с реальным актором в той же транзакции — GET /v1/m/governance/approvals/{id}/decisions это неизменяемый след, а pull-экспорт доставляет его в ваш SIEM. Вы не можете внести управляемое изменение, которое ledger молча забудет.

  • escalate_in_seconds уведомляет команду SoD, если запрос остаётся нерешённым — используйте для действий, критичных для продакшена.
  • Отмена (POST …/{id}/cancel) предназначена для запросившего или администратора по ожидающему запросу; она тоже фиксируется.
  • Что ещё дозревает — это более богатая консоль ревью; гарантии на стороне движка, описанные выше, уже работают (честный объём).