Рецепт: одобрения human-in-the-loop
Цель: «применение деплоя (либо запуск оркестрации, либо открытие голосовой сессии) не происходит, пока его не одобрит человек, который не является запросившим — и это решение становится зафиксированным фактом».
Движок одобрений работает в стандартном бинарнике; модель governance объясняет позицию. Этот рецепт — операционная обвязка.
1. Подключите шлюз одобрений
Заголовок раздела «1. Подключите шлюз одобрений»Действия модулей, которые изменили бы инфраструктуру, проходят через мост human-in-the-loop. Он включается конфигурацией — без него эти действия остаются deny-closed:
OLIVARES_APPROVAL_BRIDGE_CONFIG=/etc/olivares/approval-bridge.jsonЗапускайте компонент, который открывает одобрения, под его собственной служебной учётной записью, которой никогда нет в пуле одобряющих. Разделение обязанностей обеспечивается на стороне движка (открывший не может решить собственный запрос, а системный токен вообще не может одобрять) — если учётная запись открывшего одновременно является одобряющей, вы построили liveness-deadlock, а не контроль.
2. Откройте запрос
Заголовок раздела «2. Откройте запрос»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
этой политики — запросивший не может понизить планку со стороны запроса.
3. Примите решение
Заголовок раздела «3. Примите решение»# 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) аутентификацией на каждое решение — и этот порог структурен: политика одобрений, пытающаяся понизить уровень, заново получает порог в точке решения.
4. Запись
Заголовок раздела «4. Запись»Каждое решение добавляется в audit ledger с реальным актором в той же
транзакции — GET /v1/m/governance/approvals/{id}/decisions это неизменяемый
след, а pull-экспорт доставляет его в ваш
SIEM. Вы не можете внести управляемое изменение, которое ledger молча забудет.
Заметки
Заголовок раздела «Заметки»escalate_in_secondsуведомляет команду SoD, если запрос остаётся нерешённым — используйте для действий, критичных для продакшена.- Отмена (
POST …/{id}/cancel) предназначена для запросившего или администратора по ожидающему запросу; она тоже фиксируется. - Что ещё дозревает — это более богатая консоль ревью; гарантии на стороне движка, описанные выше, уже работают (честный объём).