Разработка HTTP-плагина

Как устроен Meridian Plugin API и как создать плагин в отдельном процессе на любом языке.

Обзор

HTTP-плагин — это обычный HTTP-сервер, который реализует Meridian Plugin API. Meridian подключается к нему по сети и передаёт вызовы тех же хуков, что и для in-process плагина: транспортных, LLM, MCP и наблюдаемости. Логика плагина выполняется в его собственном процессе, а в Meridian от плагина остаётся только адрес и список хуков.

Спецификация протокола опубликована в формате OpenAPI 3.1: Meridian Plugin API. Все сообщения — JSON, схемы описаны в JSON Schema 2020-12, поэтому по ним можно сгенерировать типы для любого языка.

Когда выбирать HTTP-плагин вместо разделяемой библиотеки:

  • Любой язык — Go, Python, Node.js, Rust: достаточно HTTP-сервера с поддержкой WebSocket.
  • Без бинарной совместимости — не нужно совпадение версии Go, зависимостей и флагов сборки с Meridian; стандартные образы Meridian подходят без изменений.
  • Изоляция — сбой или утечка памяти в плагине не затрагивает процесс шлюза; плагин можно обновлять и перезапускать независимо.
  • Кластер — один экземпляр плагина обслуживает все узлы Meridian; регистрация выполняется один раз, каждый узел подключается сам.
  • Выборочный вызов — условие вызова задаётся CEL-выражением по атрибутам запроса, поэтому плагин получает только нужный трафик.

Цена — сетевой вызов на каждый хук. Для потоковых ответов это вызов на каждый фрагмент, поэтому объявляйте только те хуки, которые действительно нужны.

Предварительные требования

  • Любой стек, на котором можно поднять HTTP/1.1-сервер с поддержкой WebSocket (RFC 6455) и JSON.
  • Сетевая доступность плагина с каждого узла Meridian: адрес вида http(s)://host:port.
  • Meridian установлен и настроен; для регистрации плагина нужны права глобального администратора.

Как устроен протокол

Операции

Каждый вызов — это POST /api/meridian/plugin/v1/{method}, где {method} — имя хука в нижнем регистре (prellmhook, httptransportprehook, init). Тело запроса и ответа — JSON-сообщения из спецификации. Кроме хуков плагин отвечает на четыре вызова жизненного цикла и две пробы:

ВызовНазначение
POST /capabilitiesСписок реализованных хуков, версия протокола (сейчас 1) и признак поддержки HookStream. Meridian вызывает его первым при каждом подключении
POST /initПередача конфигурации плагина (поле config его записи). Вызывается один раз после capabilities, если конфигурация задана; ошибка в ответе прерывает загрузку
POST /getnameСистемный идентификатор плагина
POST /cleanupОсвобождение ресурсов перед остановкой
GET /healthСостояние процесса: 200 со статусом ok или degraded, 503 — unavailable. Необязательна: при 404 Meridian использует POST /capabilities
GET /statusИмя, версия, список хуков, время работы и счётчики. Необязательна: при 404 Meridian сохраняет ответ capabilities, полученный при загрузке

Ответ capabilities для плагина, который реализует один хук:

{
  "hooks": ["PreLLMHook"],
  "protocol_version": 1,
  "stream_supported": true
}

Meridian никогда не вызывает хук, которого нет в этом списке. Также он отказывается загружать плагин с другой версией протокола или без stream_supported: true.

HookStream

GET /api/meridian/plugin/v1/hookstream переводит соединение в WebSocket. После этого каждый двоичный кадр от Meridian — одно сообщение HookStreamRequest, каждый кадр от плагина — HookStreamResponse с тем же id. Именно по этому каналу Meridian вызывает все хуки; POST-маршруты остаются для capabilities, инструментов и отладки.

Сообщение-конверт содержит id, имя вызова call и ровно одно свойство с полезной нагрузкой, имя которого — имя вызова в snake_case:

{
  "id": 42,
  "call": "PreLLMHook",
  "pre_llm_hook": {
    "ctx": { "request_id": "8d1c…", "deadline_unix_nano": 1790000000000000000, "values": {} },
    "request": { "request_type": "chat_completion", "request": { "provider": "openai", "model": "gpt-4o-mini", "input": [ { "role": "user", "content": "Hello!" } ] } }
  }
}

Ответ повторяет id и несёт ответное сообщение под тем же свойством:

{ "id": 42, "call": "PreLLMHook", "pre_llm_hook": {} }

Если вызов невозможно выполнить вовсе (неизвестный call, нечитаемое сообщение, плагин останавливается), плагин отвечает transport_error вместо полезной нагрузки:

{ "id": 42, "transport_error": "unknown call PreFooHook" }

Meridian держит отдельное соединение на каждый одновременный вызов и не перемежает запросы в одном соединении, поэтому обработчик кадров может быть простым циклом «прочитал — ответил». Неиспользуемые соединения остаются в пуле; закрывать их со стороны плагина безопасно, повторный вызов уйдёт по новому соединению.

Флаг body_changed

Каждый ответ хука несёт флаг body_changed. По умолчанию он false: Meridian сохраняет свои объекты без изменений и даже не декодирует полезную нагрузку ответа, поэтому её можно не отправлять. Это самый дешёвый ответ — пустой объект {}.

При body_changed: true полезная нагрузка ответа заменяет объекты Meridian на месте. Отсутствующее свойство при этом означает null: пустое тело, снятую ошибку, отброшенный фрагмент. Для LLM-запросов ответ можно сделать частичным: Meridian накладывает присланный JSON на свою типизированную структуру, поэтому достаточно вернуть только изменённые поля.

{
  "request": { "request_type": "chat_completion", "request": { "provider": "openrouter", "model": "deepseek/deepseek-v4-flash" } },
  "body_changed": true
}

Транспортные метаданные пересланного запроса или ответа (method, path, headers, query, status_code) применяются всегда, когда объект присутствует в ответе, независимо от флага.

Контекст запроса

Meridian передаёт в каждом хуке проекцию контекста запроса — ctx:

  • request_id — идентификатор запроса;
  • deadline_unix_nano — абсолютный срок обработки в наносекундах Unix, 0 — без срока;
  • values — значения контекста по ключам Meridian: виртуальный ключ, команда, клиент, идентификатор пользователя, тип запроса и другие. Учётные данные (ключи провайдеров, токены сессии, значение виртуального ключа) не передаются никогда;
  • plugin_scope — имя плагина, для которого сформирован контекст.

Плагин записывает в контекст через ctx в ответе — ContextDelta с полями set (установить ключи) и clear (удалить ключи). Meridian применяет их к настоящему контексту, поэтому значение, записанное в PreLLMHook, доступно тому же плагину в PostLLMHook. Зарезервированные ключи Meridian отбрасывает так же, как для in-process плагина.

{ "ctx": { "set": { "my-plugin-tenant": "acme" }, "clear": ["my-plugin-tmp"] } }

Ошибки хука и ошибки транспорта

Протокол различает три вида ошибок:

ВидКак передаётсяЧто делает Meridian
Ошибка хукаПоле error: {"message": "…"} в ответном сообщении — аналог error, который возвращает Go-хукТа же семантика, что для in-process плагина: журналируется, для PreRequestHook не блокирует запрос
Ошибка Meridian как результатBifrostError в short_circuit.error (pre-хуки) или plugin_error (post-хуки): status_code, error.type, error.message, allow_fallbacksВозвращается клиенту как ответ шлюза; allow_fallbacks: false запрещает переход на резервных провайдеров
Ошибка транспортаHTTP-статус 4xx/5xx POST-вызова, transport_error в кадре, таймаут, разрыв соединенияСчитается сбоем плагина: учитывается в circuit breaker и обрабатывается по политике on_error

Для отказа в обработке запроса используйте short_circuit.error со статусом 4xx и типом ошибки, а не HTTP-статус POST-ответа. HTTP-статусы зарезервированы под сбои транспорта: такой ответ Meridian воспримет как недоступность плагина, а не как решение по запросу.

Здоровье и отказоустойчивость

Каждый узел Meridian опрашивает плагин с периодом health_interval_seconds (по умолчанию 15 с): GET /health, а каждый N-й раз — GET /status, откуда обновляется список заявленных хуков и версия плагина. После failure_threshold (по умолчанию 5) сбоев транспорта подряд узел открывает circuit breaker и перестаёт вызывать плагин; закрывает его только успешная проба, пробных вызовов на пути запроса нет.

Политика on_error определяет поведение при сбое транспорта в pre-хуке: fail_closed (по умолчанию) отклоняет запрос с кодом 503 plugin <name> unavailable, fail_open пропускает запрос дальше с предупреждением в трассе. Post-хуки, хуки фрагментов, Inject и PreRequestHook при сбое транспорта всегда пропускают запрос: ответ уже сформирован либо хук по контракту не блокирует.

Состояние плагина ведётся на каждом узле отдельно: active, unhealthy (breaker открыт или последняя проба не прошла), error (загрузка не удалась: недоступен, другая версия протокола, нет HookStream, ошибка Init, конфликт имён), disabled, loading.

Хуки и их сообщения

Каждый хук принимает ctx и данные своего этапа и отвечает сообщением с необязательными error, ctx и body_changed. Ниже — специфичные для хуков поля. Порядок выполнения и различие фаз описаны в разделе Начало работы.

Транспортные хуки

HTTPTransportPreHook получает HTTP-запрос до входа в ядро Meridian, HTTPTransportPostHook — запрос и ответ после выхода, HTTPTransportStreamChunkHook — каждый фрагмент потокового ответа.

Запрос описывается объектом RequestDetails: method, path, headers, query, path_params, client_ip, attempt (номер попытки, начиная с 1) и тело — body (JSON как есть), либо body_b64 (base64 для тела, не являющегося JSON). Ответ — ResponseDetails: status_code, headers, body или body_b64.

Ответ HTTPTransportPreHook:

  • request с body_changed: true — изменённый запрос;
  • response — досрочное завершение с готовым HTTP-ответом:
{
  "response": { "status_code": 429, "headers": { "Content-Type": "application/json" }, "body": { "error": { "message": "quota exceeded" } } }
}

Ответ HTTPTransportStreamChunkHook несёт chunk — объект StreamChunk с полями index (порядковый номер фрагмента), request_type и chunk (JSON фрагмента в формате Meridian). body_changed: true без chunk отбрасывает фрагмент.

HTTPTransportPostHook не вызывается для потоковых ответов, а HTTPTransportStreamChunkHook вызывается для каждого фрагмента. Для длинного потокового ответа это сотни сетевых вызовов; объявляйте хук фрагментов только тогда, когда плагин действительно изменяет поток.

Хуки LLM

PreRequestHook, PreLLMHook и PostLLMHook работают с типизированным запросом Meridian: объект LLMRequest содержит request_type (chat_completion, chat_completion_stream, embedding, responses и другие) и request — JSON запроса этого типа, например provider, model, input и params для chat completion.

PreRequestHook — фаза маршрутизации, один раз на запрос. Ответ содержит только request с body_changed; досрочного завершения нет, ошибка хука не блокирует запрос. Пример из плагина маршрутизации, который меняет провайдера и модель:

{
  "request": { "request_type": "chat_completion", "request": { "provider": "openrouter", "model": "deepseek/deepseek-v4-flash" } },
  "body_changed": true
}

PreLLMHook — перед каждой попыткой обращения к провайдеру. Ответ может изменить запрос (request + body_changed) или завершить обработку досрочно через short_circuit с одним из двух полей: response (готовый ответ типа request_type) или error. Пример отказа guardrail-плагина:

{
  "short_circuit": {
    "error": {
      "status_code": 400,
      "allow_fallbacks": false,
      "error": { "type": "guardrail_violation", "message": "request contains a phone number" }
    }
  }
}

PostLLMHook получает response (объект LLMResponse: request_type и response) и/или error (BifrostError провайдера). При body_changed: true ответ плагина заменяет оба объекта: response и plugin_error; отсутствующее поле означает null, так что плагин может снять ошибку провайдера, вернув только response.

Хуки MCP

PreMCPHook и PostMCPHook вызываются для каждой операции MCP-шлюза: объект MCPRequest содержит request_type (ping, list_tools, execute_tool, chat_tool_call, responses_tool_call), client_name и request. PreMCPConnectionHook и PostMCPConnectionHook — при установлении транспорта MCP-клиента: MCPConnectRequest с client_name, connection_type, auth_type, connection_string, headers. Ответы устроены так же, как у LLM-хуков: request/response с body_changed, short_circuit с response или error, plugin_error в post-хуках. В PreMCPConnectionHook заменяются только изменяемые поля — connection_string и headers.

Наблюдаемость и конфигурация

Inject получает завершённую трассу запроса: атрибуты, заголовки, записи плагинов и спаны. Хук не имеет контекста запроса и вызывается после отправки ответа клиенту, поэтому подходит для экспорта в системы трассировки, но не для изменения данных.

MarshalConfigForStorage и RedactConfig получают объект config и возвращают преобразованный: первый — перед сохранением конфигурации в хранилище (например, шифрование секретов), второй — перед отдачей конфигурации в API и интерфейс (маскирование).

Фильтр запросов (CEL)

Условие вызова плагина задаётся CEL-выражением при регистрации; пустое выражение означает каждый запрос. Meridian вычисляет его один раз на запрос до транспортных хуков, и один результат действует для всех хуков плагина. Запрос, не прошедший фильтр, не порождает ни одного сетевого вызова.

ПеременнаяТипОписание
method, pathstringHTTP-метод и путь
headers, querymapЗаголовки (имена в нижнем регистре) и query-параметры
client_ipstringАдрес клиента
request_typestringТип запроса Meridian, например chat_completion_stream
provider, modelstringПровайдер и модель из тела запроса
virtual_key_id, virtual_key_namestringВиртуальный ключ запроса
team_id, team_name, customer_id, customer_name, user_idstringВладелец виртуального ключа

Примеры:

request_type.startsWith("chat_completion")
path == "/v1/chat/completions" && customer_name == "acme"
headers["x-tenant"] == "premium"

Переменная, для которой нет источника (например, team_id в запросе без виртуального ключа), не вызывает ошибку: сравнение с ней просто не совпадает. Синтаксически неверное выражение отклоняется при сохранении с кодом 400. В веб-интерфейсе выражение можно собрать конструктором правил, как для правил маршрутизации.

Шаг 1. Реализуйте плагин

Минимальный плагин отвечает на четыре вызова жизненного цикла, отдаёт GET /health, обслуживает HookStream и реализует один хук. Рекомендуемый порядок работы:

  1. Сгенерируйте типы сообщений из спецификации или опишите вручную только те, что нужны вашим хукам: имена свойств — snake_case, как в схемах.
  2. Реализуйте диспетчер по имени вызова и используйте его и для POST-маршрутов, и для кадров HookStream: сообщения одинаковы, различается только конверт.
  3. Отвечайте {} (body_changed: false) всегда, когда плагин ничего не изменил; отправляйте полезную нагрузку только при изменении.
  4. Для решений по запросу используйте short_circuit, для сбоев самого плагина — error в сообщении. Не используйте HTTP-статусы для бизнес-логики.
  5. Печатайте при запуске строку MERIDIAN|1|tcp|<адрес> — по ней инструменты и будущий управляемый режим находят плагин.

Готовые реализации на Go, которые можно взять за основу: минимальный http-plugin-example-go, а также плагин guardrail и плагин маршрутизации, разобранные в отдельных руководствах.

Шаг 2. Проверьте плагин без Meridian

POST-маршруты позволяют проверить плагин обычным curl:

curl -s -X POST http://localhost:18081/api/meridian/plugin/v1/capabilities -d '{}'

curl -s -X POST http://localhost:18081/api/meridian/plugin/v1/prellmhook -d '{
  "ctx": {"request_id": "r1"},
  "request": {"request_type": "chat_completion",
              "request": {"provider": "openai", "model": "gpt-4o-mini", "input": [{"role": "user", "content": "hi"}]}}
}'

curl -s http://localhost:18081/api/meridian/plugin/v1/health

Первый вызов должен вернуть список хуков с protocol_version: 1 и stream_supported: true, второй — ответ хука ({} для плагина, который ничего не меняет), третий — {"status":"ok"}.

Шаг 3. Зарегистрируйте плагин в Meridian

  1. Перейдите в Settings → Plugins.
  2. Нажмите Add HTTP plugin.
Форма Add HTTP Plugin: URL, хуки, фильтр запросов, политика при ошибке
  1. Укажите Name и URL — схему, хост и порт плагина без пути.
  2. В поле Hooks выберите хуки, которые реализует плагин. Вызываются только хуки, объявленные здесь и заявленные плагином в capabilities.
  3. При необходимости задайте Request filter — конструктором или CEL-выражением.
  4. Выберите On transport error: Fail closed или Fail open, и Placement относительно встроенных плагинов.
  5. В свёрнутых разделах доступны лимиты (таймаут вызова, число соединений, порядок), заголовки, которые Meridian отправляет плагину, и параметры TLS.
  6. Нажмите Add. Meridian сразу подключается к плагину; статус, заявленные и эффективные хуки и здоровье по узлам видны в деталях плагина.
curl -X POST http://localhost:8080/api/plugins \
  -H "Content-Type: application/json" \
  -d '{
    "plugin_architecture": "http",
    "name": "my-http-plugin",
    "enabled": true,
    "url": "http://plugin-host:18081",
    "capabilities": ["PreLLMHook", "PostLLMHook"],
    "cel": "request_type.startsWith(\"chat_completion\")",
    "on_error": "fail_closed",
    "placement": "post_builtin",
    "headers": { "Authorization": "Bearer ${PLUGIN_TOKEN}" },
    "config": { "mode": "block" }
  }'

Ответ:

{
  "message": "Plugin created successfully",
  "plugin": {
    "name": "my-http-plugin",
    "enabled": true,
    "plugin_architecture": "http",
    "placement": "post_builtin",
    "http": {
      "url": "http://plugin-host:18081",
      "cel": "request_type.startsWith(\"chat_completion\")",
      "capabilities_declared": ["PreLLMHook", "PostLLMHook"],
      "capabilities_reported": ["PreLLMHook", "PostLLMHook"],
      "capabilities_effective": ["PreLLMHook", "PostLLMHook"],
      "on_error": "fail_closed",
      "protocol_version": 1,
      "plugin_version": "1.1.0",
      "reported_name": "MyPlugin",
      "headers": { "Authorization": "***" },
      "health": { "status": "active", "breaker": "closed", "consecutive_failures": 0, "open_conns": 1 }
    }
  }
}
ПолеТипОбязательноеОписание
plugin_architecturestringДаhttp
namestringДаИмя записи; под ним плагин виден в API, интерфейсе и Plugin Logs
urlstringДаhttp(s)://host[:port] без пути
capabilitiesarrayДаОбъявляемые хуки
celstringНетФильтр запросов; пусто — каждый запрос
on_errorstringНетfail_closed (по умолчанию) или fail_open
placement, orderstring, integerНетМесто относительно встроенных плагинов и порядок внутри группы
call_timeout_msintegerНетТаймаут одного вызова; ограничен сроком запроса
max_connsintegerНетПредел одновременных вызовов плагина с одного узла
headersobjectНетЗаголовки, которые Meridian отправляет при открытии HookStream и в пробах; хранятся зашифрованно, в ответах API маскируются
tlsobjectНетinsecure_skip_verify, ca_pem — для https с собственным центром сертификации
configobjectНетКонфигурация, передаваемая в init

Другие операции: GET /api/plugins, GET /api/plugins/{name}, PUT /api/plugins/{name} (полная замена записи), DELETE /api/plugins/{name}, POST /api/plugins/{name}/probe — проба немедленно. В кластере параметр ?target=<узел> показывает состояние плагина на другом узле. Архитектуру существующего плагина изменить нельзя.

{
  "plugins_http": {
    "health_interval_seconds": 15,
    "call_timeout_ms": 2000,
    "failure_threshold": 5,
    "plugins": [
      {
        "name": "my-http-plugin",
        "enabled": true,
        "version": 1,
        "url": "http://plugin-host:18081",
        "capabilities": ["PreLLMHook", "PostLLMHook"],
        "cel": "request_type.startsWith(\"chat_completion\")",
        "on_error": "fail_closed",
        "placement": "post_builtin",
        "headers": { "Authorization": "Bearer ${PLUGIN_TOKEN}" },
        "config": { "mode": "block" }
      }
    ]
  }
}

Записи plugins_http.plugins[] имеют те же поля, что и API, плюс version: увеличение номера при следующем запуске принудительно обновляет запись в базе. В значениях headers раскрываются переменные окружения ${ENV}.

Параметры блока plugins_http действуют на все HTTP-плагины узла:

ПолеПо умолчаниюОписание
health_interval_seconds15Период проб /health на каждом узле
health_timeout_seconds3Таймаут пробы
status_every_n_probes4Каждая N-я проба обновляет /status
dial_timeout_seconds5Таймаут установления соединения
call_timeout_ms2000Таймаут вызова хука по умолчанию
load_timeout_seconds10Таймаут загрузки плагина (capabilities, init, getname)
cleanup_timeout_seconds2Таймаут cleanup при остановке
max_idle_conns16Соединений HookStream в пуле на плагин
failure_threshold5Сбоев транспорта подряд до открытия circuit breaker
max_frame_bytes64 МиБМаксимальный размер кадра

HTTP-плагины описываются только в plugins_http.plugins[]. Запись HTTP-плагина в общем массиве plugins[] Meridian воспримет как неизвестный встроенный плагин.

Шаг 4. Проверьте работу

Выполните запрос, который проходит фильтр плагина:

curl -X POST http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <virtual key>" \
  -d '{
    "model": "openai/gpt-4o-mini",
    "messages": [{"role": "user", "content": "Hello!"}]
  }'

Затем проверьте:

  • Детали плагина на странице Settings → Plugins: статус active, эффективные хуки, таблица здоровья по узлам, кнопка Probe now.
  • Plugin Logs в деталях запроса (LLM Logs → запрос): Meridian записывает строку под именем плагина для каждого вызова, в котором плагин что-то изменил — PreLLMHook: request changed, PreLLMHook: short-circuit 400 guardrail_violation: …, PostLLMHook: response changed; для потоковых ответов одна итоговая строка на поток. Вызов, который ничего не изменил, строки не оставляет.
  • Лог самого плагина: примеры печатают входящее и исходящее сообщение каждого вызова.

Эксплуатация

  • Аутентификация плагина. Meridian отправляет настроенные headers при открытии HookStream и в пробах; плагин сам решает, требовать ли их. Для сетей общего доступа задайте Authorization: Bearer <токен> и проверяйте его на upgrade-запросе.
  • TLS. Для https-адреса с сертификатом собственного центра укажите tls.ca_pem; insecure_skip_verify допустим только в тестовых средах.
  • Таймауты. Вызов хука ограничен call_timeout_ms и сроком самого запроса. Плагин, который не укладывается в лимит, считается сбойным: следите, чтобы медленная работа (обращения к внешним сервисам, тяжёлые модели) не открывала circuit breaker.
  • Кластер. Регистрация выполняется один раз; каждый узел подключается к тому же адресу и ведёт собственное состояние здоровья. Плагин должен быть готов к нескольким одновременным соединениям с разных узлов.
  • Идентификатор. Имя записи в Meridian — это имя, под которым плагин виден везде; имя, возвращаемое getname, сохраняется как reported_name и служит для сверки.
  • Обновление. Плагин можно перезапускать независимо от Meridian: соединения переустанавливаются, а на время недоступности действует политика on_error. Изменение записи (PUT) заново загружает плагин: capabilities → init → getname.

Диагностика

Статус error сразу после регистрации

Причины: адрес недоступен с узла Meridian; capabilities вернул protocol_version, отличный от 1, или stream_supported: false; init ответил ошибкой; другой плагин уже использует то же имя.

Решение: проверьте POST /capabilities с узла Meridian (curl из контейнера или хоста шлюза), исправьте ответ плагина, при необходимости выполните Probe now — узел повторяет загрузку при следующей пробе.

Хук не вызывается

Причины: хук не объявлен в записи плагина; хук не заявлен плагином в capabilities (в деталях плагина он есть в capabilities_declared, но отсутствует в capabilities_reported и capabilities_effective); запрос не проходит CEL-фильтр; хук HTTPTransportPreAuthHook не вызывается в текущей версии.

Решение: сверьте три списка в деталях плагина и проверьте выражение фильтра на запросе, который должен проходить.

Клиент получает 503 plugin <name> unavailable

Причина: сбой транспорта в pre-хуке при политике fail_closed: таймаут, разрыв соединения, кадр больше max_frame_bytes, ответ transport_error, открытый circuit breaker.

Решение: смотрите last_probe_error и consecutive_failures в здоровье плагина, лог плагина на предмет ошибок разбора сообщения; на время устранения переключите плагин в fail_open, если пропуск запросов допустим.

Статус unhealthy

Причина: circuit breaker открыт после серии сбоев или последняя проба /health не прошла.

Решение: breaker закрывает только успешная проба; после восстановления плагина статус вернётся к active в течение health_interval_seconds.

Изменение запроса не применяется

Причина: в ответе нет body_changed: true — Meridian не декодирует полезную нагрузку, если флаг не установлен.

Решение: возвращайте body_changed: true вместе с изменённым объектом; помните, что при этом отсутствующие свойства означают null.

Что дальше

Содержание