Перейти до вмісту

Протокол модуля ↔ 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. Записи не оголошувати до проходження цих перевірок.

Джерела й пов’язані роботи ​