Очередь сверки внешних эффектов
Когда 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 (решение по одному списанию). Обоим нужны права администратора.
Гарантии те же, что у очереди примирения: каждое решение — отдельное событие аудита, решение окончательно, повтор возвращает первое решение, очередь никогда не смешивает пространства имён.
Гарантии
- Каждое решение записывается в журнал аудита.
- Решение окончательно: повтор того же решения безвреден, противоречащее — отклоняется.
- Истёкшее взятие можно перехватить; устаревшее взятие больше ничего не решает.
- Очередь никогда не пересекает границы пространств имён.