Документация Очередь сверки эффектов

Очередь сверки внешних эффектов

Когда Mockarty выполняет платное или необратимое внешнее действие — вызов AI-модели, развёртывание, задание на платном раннере — он фиксирует точный итог. Иногда итог невозможно увидеть: сеть оборвалась посреди вызова, запуск отменили после отправки запроса, провайдер не ответил. В такой ситуации Mockarty никогда не угадывает. Действие остаётся в состоянии неопределённости, его бюджет остаётся зарезервированным, а само действие попадает в очередь сверки эффектов, пока оператор не подтвердит, что произошло на самом деле.

Эта страница показывает, как администратор просматривает и разрешает такие записи.

Что содержит очередь

Каждая запись — одно внешнее действие с неопределённым итогом:

Поле Значение
executionId Уникальный идентификатор действия.
effectFamily Тип действия, например llm.chat (вызов AI-модели) или coder.deploy.apply (развёртывание).
status unknown — итог не наблюдался; operator_required — автоматическое восстановление исчерпано, решение обязательно.
reason Причина неопределённости: transport_ambiguous, lease_expired, cancelled_after_dispatch или recovery_exhausted.
claim Присутствует, когда записью уже занимается другой оператор.

Просмотр очереди

Требуются права администратора. Очередь всегда ограничена одним пространством имён.

curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:5770/api/v1/admin/effects/reconciliation?namespace=my-team&limit=50"

Ответ:

{
  "items": [
    {
      "namespace": "my-team",
      "executionId": "coderdeploy-0f3a…",
      "effectFamily": "coder.deploy.apply",
      "status": "unknown",
      "reason": "transport_ambiguous",
      "createdAt": "2026-08-30T10:00:00Z",
      "updatedAt": "2026-08-30T10:05:00Z"
    }
  ],
  "nextCursor": ""
}

Полезные фильтры: family (только один тип действия), reason, minAgeSeconds (только записи, неразрешённые как минимум столько секунд) и cursor для следующей страницы.

Курсор привязан к пространству имён и ко всем фильтрам, с которыми он был создан. Если область выборки изменилась, начните без курсора: Mockarty отклонит курсор от другой области вместо того, чтобы незаметно пропустить записи.

Взятие записи в работу

Взятие в работу гарантирует, что два оператора никогда не решат одно действие одновременно. Взятие временное — если вы отвлеклись, оно истекает, и запись сможет взять другой оператор.

curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"namespace":"my-team","executionId":"coderdeploy-0f3a…"}' \
  http://localhost:5770/api/v1/admin/effects/reconciliation/claim

В ответе придут claimToken и claimGeneration — сохраните оба. Пока разбираетесь, продлевайте взятие через POST /api/v1/admin/effects/reconciliation/heartbeat, либо верните запись в очередь через POST /api/v1/admin/effects/reconciliation/release.

Принятие решения

Сначала проверьте, что провайдер сделал на самом деле — в счёте, панели управления или ответе поддержки провайдера. Затем:

  • Провайдер доказуемо не сделал ничего платного — закройте запись решением no_effect:
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "namespace": "my-team",
    "executionId": "coderdeploy-0f3a…",
    "decision": "no_effect",
    "claimToken": "<из ответа claim>",
    "claimGeneration": 1
  }' \
  http://localhost:5770/api/v1/admin/effects/reconciliation/reconcile

Для вызовов AI-моделей (llm.chat) зарезервированный бюджет освобождается тем же шагом, и обязательны два дополнительных поля: providerReference (строка счёта, операция в панели или тикет, доказывающие отсутствие эффекта) и evidenceSource (provider_invoice, provider_dashboard, provider_support или internal_effect_log).

  • Действие на самом деле произошло — это здесь заявить нельзя, и так задумано. Подтверждённый состоявшийся итог оформляется через собственный поток сверки той функции, которая производила действие (например, подтверждение итога развёртывания у кодер-миссии) — он записывает нужные ему доказательства провайдера.

Можно взять запись и решить её одним вызовом, передав "autoClaim": true вместо токена взятия.

Из командной строки

mockarty-cli effects queue --namespace my-team
mockarty-cli effects reconcile-no-effect coderdeploy-0f3a… \
  --namespace my-team --provider-reference invoice-77 --evidence-source provider_invoice

Из SDK

Пространство имён клиента подставляется автоматически. Для этих методов нужен токен администратора.

Go SDK

page, err := client.EffectReconciliation().ListQueue(ctx, mockarty.EffectReconciliationListOptions{
    EffectFamily: "llm.chat",
    Limit:        50,
})
if err != nil {
    return err
}

result, err := client.EffectReconciliation().ReconcileNoEffect(
    ctx, page.Items[0].ExecutionID, "invoice-77", "provider_invoice",
)

Python SDK

page = client.effect_reconciliation.list_queue(effect_family="llm.chat", limit=50)
result = client.effect_reconciliation.reconcile_no_effect(
    page["items"][0]["executionId"],
    provider_reference="invoice-77",
    evidence_source="provider_invoice",
)

Java SDK

Map<String, Object> page = client.effectReconciliation()
    .listQueue(null, "llm.chat", null, 0, 50, null);
Map<String, Object> result = client.effectReconciliation()
    .reconcileNoEffect(executionId, "invoice-77", "provider_invoice");

Для AI-агентов

Та же очередь доступна по MCP через два инструмента: effect_reconciliation_queue (что ждёт решения) и effect_reconcile_no_effect (закрыть одну запись как доказанное отсутствие эффекта с указанием доказательств). Обоим нужны права администратора.

Списания после отмены миссии

Бывает, что провайдер доводит вызов модели до конца и выставляет за него счёт, хотя миссию отменили, пока вызов был в пути. Сам Mockarty такое списание не возвращает никогда: решает администратор, посмотрев на доказательства. До решения списание лежит в своей очереди: Администрирование → Эффекты → Списания после отмены.

Каждый элемент — карточка доказательств: сумма и валюта, провайдер и модель, кто и почему отменил миссию, квитанция провайдера о том, что он сделал, и поле ineligible. Если ineligible пустое, доказательств достаточно и можно решать. Иначе поле называет, почему решение пока невозможно, и карточка остаётся в очереди:

ineligible Что это значит
no_effect_receipt Нет квитанции о том, что сделал провайдер.
receipt_not_settled Результат у провайдера ещё не установлен.
evidence_not_exact Квитанция не доказывает точно, что сделал провайдер.
contradictory_evidence Две записи противоречат друг другу.
cancellation_missing Отмена миссии не найдена.
already_refunded Это списание уже возвращено.
already_decided По этому списанию уже принято решение.
hosted_wallet_refund_pending Списание оплачено из кошелька Mockarty Cloud, но у этой установки нет связи с Mockarty Cloud, через которую деньги могли бы вернуться, поэтому решение не предлагается.

Напишите причину (от 3 до 500 символов) и нажмите Одобрить возврат или Отклонить. Одобрение возвращает деньги в том же шаге, отказ оставляет списание. В обоих случаях решение окончательно, списание уходит из очереди, а владелец миссии получает уведомление: сумма, решение и ваша причина. Если доказательства изменились, пока карточка была открыта, решение не примется и очередь обновится — посмотрите карточку заново.

Если списание оплачено из кошелька клиента в Mockarty Cloud, одобрение возвращает деньги в этот кошелёк. Уведомление после Одобрить возврат сообщает, зачислены ли они уже или ещё в пути; если Mockarty Cloud недолго недоступен, возврат сам повторяется, пока кошелёк не будет пополнен. Если Mockarty Cloud отказал в зачислении, уведомление назовёт причину.

AI-агенты работают с той же очередью через два MCP-инструмента: refund_candidates (очередь или одна карточка) и refund_decide (решение по одному списанию). Обоим нужны права администратора.

Гарантии те же, что у очереди примирения: каждое решение — отдельное событие аудита, решение окончательно, повтор возвращает первое решение, очередь никогда не смешивает пространства имён.

Гарантии

  • Каждое решение записывается в журнал аудита.
  • Решение окончательно: повтор того же решения безвреден, противоречащее — отклоняется.
  • Истёкшее взятие можно перехватить; устаревшее взятие больше ничего не решает.
  • Очередь никогда не пересекает границы пространств имён.