0061. Персистентная очередь подтверждений планов ИИ-агента
Копия ADR из репозитория:
backend/docs/adr/0061-ai-agent-persistent-approvals.md. Канонический текст — в репозитории; портал обновляется командойnpm run sync:adr.
- Статус: accepted
- Дата: 2026-09-28
- Связанные задачи: OP-126/127 (5.10); опирается на ADR-0054 (управление кампанией), OP-45 (журнал аудита)
Контекст и проблема
Подтверждение действий агента было stateless: POST /ai-agent/tasks/plan
возвращал план, фронт держал его в памяти и сам отправлял действия в
POST /ai-agent/actions/execute. После перезагрузки страницы очередь
пропадала, отклонение нигде не фиксировалось, а история
(GET /ai-agent/actions/history) не различала кабинеты.
Рассмотренные варианты
- Хранить очередь только в аудите — нет состояния «ожидает решения», атомарно решить один раз нельзя.
- Отдельная таблица
ai_agent_approvals— статусpending → approved | rejected, условный UPDATE гарантирует однократное решение.
Решение
Вариант 2. План с действиями сохраняется при планировании (в ответ добавлен
approvalId); действия хранятся с уже подставленными advertId/nmId из
контекста задачи. approve атомарно переводит статус и выполняет действия
через AgentActionExecutorService (guardrails и аудит — там), итог
сохраняется в execution_result. reject пишет аудит
ai-agent.action.rejected (metadata.source = 'user'). Повторное решение —
409. Доступ — по кабинету (WbAccountsService.getById), чужое → 404.
Все аудит-записи агента теперь несут metadata.wbAccountId (и approvalId,
если выполнение из подтверждения) — по нему история фильтруется по кабинету.
pause_campaign выключает автобиддер кампании (autoEnabled=false,
ADR-0054), а не ставит кампанию на паузу в WB: агент прекращает автоторговлю,
откат включает её обратно.
Последствия
- Положительные: очередь переживает перезагрузку, решение однократно, история скоупится по кабинету.
- Отрицательные / компромиссы: записи аудита до этого изменения не имеют
metadata.wbAccountIdи под фильтр по кабинету не попадают.POST /ai-agent/actions/executeоставлен для обратной совместимости. - Дальше: истечение (
expired) старых pending — при необходимости.