Перейти к содержимому

Протокол модуля ↔ API ​

Границы и статус ​

SD Module REST 1.0 · Актуальный контракт для разработки · 1 октября 2026. Это первая версия REST-протокола модуля/API. Предыдущие документы Bridge RPC были исследовательскими черновиками и не накладывают требований совместимости или миграции. Описанные эндпоинты ещё нужно реализовать; открытые технические детали перечислены ниже.

Модуль — адаптер платформы, без LLM, harness или MCP-сервера. Agentfy обращается к MCP Store Deputy; Store Deputy проверяет полномочия, преобразует инструменты в запросы этого протокола и вызывает модуль. Модуль проверяет подключение и локальные права, применяет правила платформы и возвращает доказательства результата. Магазин остаётся источником истины.

REST 1.0 / СХЕМА ПРОЦЕССАОдин запрос. Чёткие обязанности.Нажмите на карточку для пояснения
Понимает запрос

Harness запускает агента. Модель предлагает бизнес-аргументы, а не ключи или права.

catalog.setBasePrice

Квитанция возвращается в чат. Данные принадлежат магазину.

Жизненный цикл подключения ​

REST 1.0 / СХЕМА ПРОЦЕССАОт Connect до готового агента.Нажмите на карточку для пояснения
Connect

Авторизованный администратор создаёт одноразовый challenge. Браузер передаёт ссылку и CSRF state, не секреты магазина.

pending

Привязка и создание агента — разные этапы. Повтор создания не должен давать дубликат.

  1. Администратор с правом OpenCart modify запускает Connect. Модуль создаёт одноразовый challenge со сроком действия; браузер передаёт только ссылку на привязку и CSRF state.
  2. Вход/регистрация на сайте определяет команду и явно подтверждает магазин. Сервис проверяет членство, доказательство владения, точный endpoint магазина и разрешённый URL возврата. Секреты не передаются в query-параметрах редиректа.
  3. POST /pairings — ресурс первоначального подключения на стороне модуля под REST-базой /v1. Доверенный модулю ключ бекенда аутентифицирует межсерверный обмен до появления ключей подключения. Модуль проверяет и атомарно погашает одноразовый challenge, привязывает подтверждённые connection/store и устанавливает ключи подключения. Отдельные ключи направлений выводятся через согласованный KDF; схемы bootstrap, векторы подписи/KDF и key ID нужно определить до запуска. Это исключение первоначального подключения не разрешает бизнес-операций.
  4. API читает защищённый REST manifest, проверяет идентичность, версию и возможности, затем создаёт агента в Agentfy с центральным MCP endpoint. Успех привязки и создания агента — разные состояния. Повтор создания использует то же подключение, без дублирования агентов.
  5. Локальная Pause сразу блокирует бизнес-чтения и записи; подписанные manifest, статус операций и отзыв остаются доступны. Отзыв отклоняет старые ключи, отменяет отправку ожидающих команд и привязки покупателей. Уже выполненные записи не откатываются. Ротация имеет key ID и ограниченное окно перехода; отозванный ключ не принимается даже в этом окне.

Сохраняем pending → paired → ready, а также paused, revoked, incompatible, provisioningFailed. Неудачная проверка manifest не даёт ready. Ошибку создания агента можно исправить без повторной привязки.

Транспорт и аутентификация ​

Вне изолированных локальных fixtures обязателен HTTPS с проверкой сертификата. JSON — UTF-8, без сжатия в этом профиле; ответы имеют Cache-Control: no-store. Логические маршруты ниже относительны к зарегистрированному базовому URL модуля, например https://shop.example/storedeputy.php/v1. PATH_INFO/front-controller нужно проверить на хостингах; перехода к неограниченному admin router OpenCart нет. Редиректы отклоняются. Проверка DNS и адресов должна предотвращать SSRF, включая private/loopback/link-local, rebinding и перенаправления.

Предлагаемый профиль: HTTP Message Signatures RFC 9421, hmac-sha256, отдельные ключи направлений для каждого подключения; Content-Digest RFC 9530 с SHA-256 точных переданных байтов, включая пустое тело. Подпись охватывает @method, @target-uri, content-digest, x-sd-protocol, x-sd-connection, x-sd-store, x-request-id; для изменений также idempotency-key. Обязательные параметры keyid, created, expires, nonce; key ID определяет разрешённый алгоритм и направление, а не произвольный параметр отправителя.

Предлагаемые границы: expiry не позже 60 секунд после created, погрешность часов 30 секунд; подписи вне окна отклоняются. Nonce атомарно резервируется для ключа/направления до expiry + погрешность. Повтор имеет новую подпись/nonce/request ID, но тот же ключ операции и payload. Повторные заголовки, дубли query-параметров и неизвестные кодировки отклоняются. Proxy сохраняет зарегистрированный публичный URI; доверенные proxy настраиваются явно.

Ответы тоже подписаны: статус, digest, connection, store, request ID и исходные method/target через request-bound компоненты RFC 9421. До доверия квитанции проверяем корреляцию, срок, digest, подпись и JSON schema. Неверная/отсутствующая подпись или HTML шлюза означает неизвестный результат, не успех записи. Общие эталонные векторы PHP/Node — условие выпуска; существующий экспериментальный код этих правил не реализует.

Обнаружение и возможности ​

GET /manifest требует аутентификации. Возвращает connection/store, версии протокола/модуля/платформы, включённые возможности, лимиты, Pause, семантику валюты/цен и предупреждения совместимости. Пример описывает будущий модуль, не текущий manifest. Объявленная возможность — реализованное и проверенное обязательство, не roadmap.

Доступ — пересечение возможности модуля, локальных разрешений владельца, роли пользователя/команды, scope агента и тарифа. API может ограничить права, но не включить отсутствующую возможность. Manifest проверяется после обновления/переподключения; каждая запись всё равно проверяет актуальные локальные права. Данные OpenCart multi-store могут быть общими: базовая цена влияет на несколько витрин. Модуль возвращает все затронутые магазины и отклоняет запись без полномочий и согласования для всех.

json
{
  "data": {
    "protocol": "1.0", "connectionId": "conn_demo", "storeId": "0",
    "moduleVersion": "1.0.0", "platform": { "name": "opencart", "version": "3.0.3.5" },
    "paused": false,
    "capabilities": ["catalog.products.read@1", "catalog.basePrice.write@1", "operations.read@1"],
    "limits": { "maxPageSize": 100, "maxBodyBytes": 262144, "maxExecutionMs": 10000 },
    "price": { "currency": "USD", "scale": 4, "taxIncluded": false, "scope": "sharedProduct" },
    "warnings": []
  },
  "meta": { "requestId": "req_demo", "protocol": "1.0" }
}

Ресурсы и соответствие MCP ​

Маршруты относительны к базе /v1 модуля. Магазин задаёт подписанный X-SD-Store, ограниченный подключением. GET не меняет бизнес-данные. Нет универсального исполнения SQL, PHP, admin-route или произвольных HTTP-запросов.

Контракт чтения этапа A: cursor (непрозрачный, необязательный), limit (1–100, по умолчанию 50), sku (необязательный точный фильтр), language (объявленная локаль). Стабильная сортировка по product ID; ответ {data: Product[], page:{nextCursor:null|string}, meta}. Cursor привязан к connection/store/filter, но не гарантирует snapshot. ID — непрозрачные строки; деньги — десятичные строки объявленной точности; валюта — ISO. Товар имеет id, sku, name, basePrice, currency, revision, affectedStoreIds и предупреждения о specials/options. SKU может дать несколько товаров: нельзя молча выбрать первый. Отсутствующий товар — 404. Сильная ревизия базовой цены учитывает внешние изменения администратора, а не только модуль; без доказательства этого записи выключены.

Создание товаров, остатки и ответы на отзывы — расширения. До публикации нужны собственные схемы, права, конкурентность и проверка результата. Первая реализация не означает доступность всех действий админки.

MCP tool / purposeREST resourceCapability / stage
Adapter discoveryGET /manifestRequired / A
catalog.listProductsGET /productscatalog.products.read@1 / A
catalog.getProductGET /products/{id}catalog.products.read@1 / A
catalog.getBasePriceGET /products/{id}/base-pricecatalog.products.read@1 / A
Approved catalog.setBasePricePOST /operations (kind=product.basePrice.set)catalog.basePrice.write@1 / controlled write
operation.getGET /operations/{operationId}operations.read@1 / A
cart.getGET /shopper-bindings/{bindingId}/cartcart.read@1 / B
Authorized cart.setItemQuantityPOST /operations (kind=cart.item.quantity.set)cart.write@1 / B, schema pending

GET /products/{id}/base-price → 200, ETag: "price-r17":

json
{"data":{"productId":"42","amount":"19.9900","currency":"USD","revision":"price-r17","affectedStoreIds":["0"]},"meta":{"requestId":"req_demo","protocol":"1.0"}}

Контролируемые записи ​

REST 1.0 / СХЕМА ПРОЦЕССАИзменение цены с квитанцией.Нажмите на карточку для пояснения
Согласовать

Владелец проверяет товар 42, ревизию, затронутые магазины и новую базовую цену. Другое изменение требует нового согласования.

19.9900 → 21.5000 USD

Одно согласованное намерение → один operation ID, сохранённый при повторах.

API определяет connectionId, магазин, пользователя, run и согласование из аутентифицированного состояния вне prompt. Модель передаёт бизнес-аргументы, а не ключи, роль, личность покупателя или флаг согласования. Для защищённой записи владелец видит точные ID, значения до/после, затронутые магазины и предупреждения; неизменяемое согласование привязывается к действию и ревизии. Изменение данных требует нового согласования. Членство, тариф, отзыв и срок согласования проверяются ещё раз перед отправкой.

POST /operations создаёт одну операцию для одного ресурса, не скрытую batch-транзакцию. API создаёт UUID один раз для согласованного намерения и использует его как Idempotency-Key; operationId в теле должен совпадать. Подписанное тело содержит доверенного пользователя и решение согласования; модуль доверяет API аутентификацию сотрудника, но сам проверяет connection/store, локальные права/Pause, точный хеш действия и срок согласования. Это не защищает от скомпрометированного доверенного API; локальные права и Pause ограничивают это доверие.

actionHash — SHA-256 канонического JSON RFC 8785 для {connectionId, storeId, kind, target, expected, set, affectedStoreIds}. Ревизия и затронутые магазины тоже связаны. Модуль вычисляет хеш и сравнивает с grant согласования; более широкие wildcard-права недопустимы. Неизвестные поля изменения, некорректные суммы, валюта, просроченное согласование, несоответствие scope и изменения без эффекта отклоняются до записи. expected.revision — бизнес-предусловие цены, не If-Match коллекции операций; несовпадение даёт 409 REVISION_CONFLICT без записи.

Исполнение: атомарная запись в журнал → проверка ресурса/предусловия → условная запись с сохранением остальных полей → чтение назад → обязательная очистка кеша → квитанция. Hooks и внешние эффекты имеют отдельный статус; успех БД не доказывает их завершения. Не перезаписываем полную форму товара или specials/discounts/options. Автоотката нет: возврат — новая согласованная операция на текущей ревизии.

POST /operations · Idempotency-Key: 4faf6b8a-887e-4c97-93b0-a5476ae95a75

json
{
  "operationId": "4faf6b8a-887e-4c97-93b0-a5476ae95a75",
  "connectionId": "conn_demo", "storeId": "0",
  "kind": "product.basePrice.set", "target": { "productId": "42" },
  "expected": { "revision": "price-r17", "amount": "19.9900" },
  "set": { "amount": "21.5000", "currency": "USD" },
  "affectedStoreIds": ["0"],
  "context": { "actorId": "user_demo", "actorKind": "employee", "runId": "run_demo" },
  "approval": { "id": "apr_demo", "actionHash": "<sha256-hex>", "expiresAt": "2026-10-01T12:05:00Z" }
}

201 Created · Location: https://shop.example/storedeputy.php/v1/operations/4faf6b8a-887e-4c97-93b0-a5476ae95a75

json
{
  "data": {
    "operationId": "4faf6b8a-887e-4c97-93b0-a5476ae95a75",
    "state": "succeeded", "actionHash": "<sha256-hex>",
    "target": { "productId": "42" },
    "before": { "amount": "19.9900", "revision": "price-r17" },
    "after": { "amount": "21.5000", "currency": "USD", "revision": "price-r18" },
    "verifiedAt": "2026-10-01T12:00:01Z",
    "effects": { "cache": "completed", "hooks": "notRequired" }, "warnings": []
  },
  "meta": { "requestId": "req_demo", "protocol": "1.0" }
}

Повторы, конфликты и неизвестный результат ​

REST 1.0 / СХЕМА ПРОЦЕССАНет ответа — не значит нет изменения.Нажмите на карточку для пояснения
Таймаут / потеря связиGET /operations/{id}Прочитайте журнал перед решением
Результат подтверждён

Завершённая запись доказывает успех, конфликт или отказ. Возвращаем настоящий результат без повторного исполнения.

succeeded / conflict / failed

Не создавайте новый ключ и не повторяйте вслепую. Совпадение цены не доказывает нашу запись.

Журнал имеет атомарный уникальный ключ (connectionId, storeId, operationId) и неизменяемый хеш полного смыслового тела операции. Тот же ключ/тело возвращает сохранённый результат с новой транспортной подписью; другое тело — 409 IDEMPOTENCY_MISMATCH. Повтор активной операции даёт 202 с тем же URL и Retry-After, не запускает другого исполнителя. Повтор завершённой — 200. Даже кешированный результат требует действующих прав подключения; просроченное согласование не начинает и не возобновляет новые эффекты.

GET /operations/{id} возвращает {data: Operation, meta}, где state: pending, running, succeeded, conflict, failed, unknown. Успех содержит квитанцию; conflict/failed — стабильный code и доказательство отсутствия эффекта; unknown — известные частичные эффекты и причину сверки. Pause разрешает авторизованное чтение статуса. Отсутствующая запись — 404 OPERATION_NOT_FOUND, но это не доказательство отсутствия исторического эффекта.

После таймаута, 5xx шлюза, падения процесса или отсутствующей подписи сначала читаем статус. Без нового ключа и слепого повтора. Tombstone запрещает повторное исполнение после архивирования квитанции. ID/хеш операции храним бессрочно в REST 1.0; персональное содержимое очищаем/архивируем отдельно. Потеря журнала или восстановление backup нарушает полноту: записи останавливаются до сверки. Повтор неизвестного ключа конкурирует с исходным исполнителем через тот же атомарный claim.

currentPrice == requestedPrice не доказывает наше исполнение: администратор мог изменить цену так же. Устаревший running не разрешает другого писателя. Сверяем устойчивые доказательства операции; иначе оставляем unknown и требуется расследование. Не обещаем exactly-once между ценой, журналом и hooks без доказательства crash boundary. Для нетранзакционных таблиц — только чтение, пока не доказано безопасное восстановление. Несколько товаров — отдельные операции с собственными результатами, не атомарный batch.

Расширение покупателя и корзины ​

REST 1.0 / СХЕМА ПРОЦЕССАНастоящая корзина покупателя.Нажмите на карточку для пояснения
Определить локально

Widget вызывает модуль на origin магазина. Модуль определяет гостя или покупателя; сырые cookies остаются в магазине.

same-origin bootstrap

Вход, выход или слияние корзины требуют обновления или отзыва привязки. Расширение этапа B.

Этап B имеет отдельный профиль; учётных данных сотрудника недостаточно. Widget обращается к same-origin endpoint модуля с защитой от CSRF. Модуль определяет личность из действующей сессии витрины и передаёт аутентифицированное подтверждение Store Deputy. API возвращает непрозрачную привязку и краткосрочный доступ к конкретному разговору. Сырые cookies сессии и админ-ключи не передаются widget или Agentfy.

Серверная привязка ограничена connection, store, поколением сессии, покупателем/гостем, разговором, действиями корзины и сроком. API и модуль проверяют её каждый раз. Модель не выбирает binding или customer ID. Login/logout, expiry и слияние гостевой корзины требуют отзыва/перепривязки; изменение в другой вкладке делает ревизию неактуальной. Модуль использует настоящую корзину OpenCart и расчёты платформы, не отдельную API-корзину.

Лучше желаемое количество (установить 3), чем добавление при каждом повторе. Изменение корзины требует ключа операции, ожидаемой ревизии и явного намерения покупателя относительно точных товаров/опций; админ-согласование не заменяет владение корзиной. Возвращаются проверка позиций, подтверждённая ревизия и пересчитанные totals для обновления витрины. Схемы bootstrap/refresh, hooks смены сессии и квитанции нужно зафиксировать на этапе B до включения cart.*. Checkout, платежи и создание заказов исключены.

Ошибки и лимиты ​

Ошибки имеют application/problem+json по RFC 9457 и стабильные расширения code, requestId, operationId, если известен. Клиент использует status/code, не переведённый текст. Без SQL, stack traces, секретов или сессионных ID. Размер проверяется до разбора; неизвестные поля изменения и неправильные типы отклоняются. API и модуль ограничивают частоту на connection; manifest-лимиты — технические, не тарифные. Учитываем Retry-After; чтения повторяем с ограниченной задержкой, записи сначала сверяем. 202 — принято/исполняется, не завершено. 200 с неверной схемой не является успехом.

HTTPStable codeMeaning / next step
400INVALID_REQUESTMalformed transport/JSON; fix request
401AUTHENTICATION_FAILEDInvalid/expired/replayed signature; no effect
403SCOPE_DENIED, CONNECTION_REVOKED, STORE_PAUSED, APPROVAL_REQUIREDNo new effect; restore authority explicitly
404RESOURCE_NOT_FOUND, OPERATION_NOT_FOUNDCheck identity; do not infer write outcome
409REVISION_CONFLICT, IDEMPOTENCY_MISMATCHNo new write; inspect conflict
413BODY_TOO_LARGEReduce request size
422VALIDATION_FAILED, CAPABILITY_UNAVAILABLEFix data or disable tool
429RATE_LIMITEDRespect Retry-After
503TEMPORARILY_UNAVAILABLERead back operation before write retry
json
{"type":"about:blank","title":"Conflict","status":409,"detail":"The approved price revision is no longer current.","code":"REVISION_CONFLICT","requestId":"req_demo","operationId":"4faf6b8a-887e-4c97-93b0-a5476ae95a75"}

Совместимость и проверка реализации ​

До параллельной реализации обмена зафиксировать OpenAPI + JSON Schemas и общие fixtures в STOR-17. Страница определяет целевую семантику, не готовый SDK или набор тестов. Новые необязательные поля ответа добавляются совместимо; неизвестные возможности выключены. Неизвестное состояние операции — неразрешённое, не успех. Несовместимая семантика требует нового major; REST 1.0 не имеет режима совместимости со старым RPC.

Условия выпуска:

  • Векторы подписи и action hash для PHP/Node; изменение method/path/query/body/status нарушает проверку; гонки nonce, время, ротация и отзыв.
  • Fixtures OpenCart 3.0.3.5 и 3.0.5.1, переименованная admin-папка и маршрутизация хостингов; непроверенные версии выключены. OpenCart 4 — отдельный адаптер и матрица.
  • Запрет чужой команды/магазина, общие товары, Pause, просроченное согласование, подставленная в prompt личность и вредоносный текст каталога.
  • Пагинация/дубли SKU, точные суммы, конфликт ручной правки, включая изменение и возврат (ABA); сохранение связей товара и предупреждений о цене витрины.
  • Конкурентные повторы имеют один claim; другой payload с тем же ключом отклоняется; остановка процесса до/после записи, read-back, кеша и квитанции. Доказательство восстановления unknown вместо выдуманного успеха.
  • Логи связывают connection/run/request/operation без секретов; аудит API хранит согласование, хеш и квитанцию. События модуля не являются отдельным счётчиком биллинга Agentfy.
  • Этап B отдельно доказывает принадлежность корзины гостю/покупателю, logout/merge/revocation, вкладки, повтор изменения количества и обновление витрины.

Ответственные: Artem Belikov — модуль/PHP; yevhen.kariakin — API, MCP-адаптер и общий контракт. Обе стороны используют одинаковые fixtures. До фиксации остаются точные signing/KDF vectors, публичная маршрутизация, безопасные ревизии/восстановление для нетранзакционных магазинов, политика hooks и схемы этапа B. Записи не объявлять до прохождения этих проверок.

Источники и связанные работы ​