Протокол модуля ↔ API
Межі та статус
SD Module REST 1.0 · Актуальний контракт для розробки · 1 жовтня 2026. Це перша версія REST-протоколу модуля/API. Попередні документи Bridge RPC були дослідницькими чернетками й не накладають вимог сумісності чи міграції. Описані ендпоїнти ще потрібно реалізувати; відкриті технічні деталі наведено нижче.
Модуль — адаптер платформи, без LLM, harness чи MCP-сервера. Agentfy звертається до MCP Store Deputy; Store Deputy перевіряє повноваження, перетворює інструменти на запити цього протоколу й викликає модуль. Модуль перевіряє підключення й локальні права, застосовує правила платформи та повертає докази результату. Магазин залишається джерелом правди.
Harness запускає агента. Модель пропонує бізнес-аргументи, а не ключі чи права.
catalog.setBasePriceКвитанція повертається до чату. Дані належать магазину.
Життєвий цикл підключення
Авторизований адміністратор створює одноразовий challenge. Браузер передає посилання й CSRF state, не секрети магазину.
pendingПрив’язка й створення агента — різні етапи. Повтор створення не має давати дублікат.
- Адміністратор з правом OpenCart
modifyпочинає Connect. Модуль створює одноразовий challenge з терміном дії; браузер передає лише посилання на прив’язку та CSRF state. - Вхід/реєстрація на сайті визначає команду й явно підтверджує магазин. Сервіс перевіряє членство, доказ володіння, точний endpoint магазину й дозволений URL повернення. Секрети не передаються в query-параметрах редиректу.
POST /pairings— ресурс початкового підключення на стороні модуля під REST-базою/v1. Довірений модулю ключ бекенда автентифікує міжсерверний обмін до появи ключів підключення. Модуль перевіряє й атомарно погашає одноразовий challenge, прив’язує підтверджені connection/store і встановлює ключі підключення. Окремі ключі напрямків виводяться через погоджений KDF; схеми bootstrap, вектори підпису/KDF і key ID потрібно визначити до запуску. Цей виняток початкового підключення не дозволяє бізнес-операцій.- API читає захищений REST manifest, перевіряє ідентичність, версію та можливості, потім створює агента в Agentfy з центральним MCP endpoint. Успіх прив’язки й створення агента — різні стани. Повтор створення використовує те саме підключення, без дублювання агентів.
- Локальна 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 можуть бути спільними: базова ціна впливає на кілька вітрин. Модуль повертає всі зачеплені магазини й відхиляє запис без повноважень і погодження для всіх.
{
"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 / purpose | REST resource | Capability / stage |
|---|---|---|
| Adapter discovery | GET /manifest | Required / A |
catalog.listProducts | GET /products | catalog.products.read@1 / A |
catalog.getProduct | GET /products/{id} | catalog.products.read@1 / A |
catalog.getBasePrice | GET /products/{id}/base-price | catalog.products.read@1 / A |
Approved catalog.setBasePrice | POST /operations (kind=product.basePrice.set) | catalog.basePrice.write@1 / controlled write |
operation.get | GET /operations/{operationId} | operations.read@1 / A |
cart.get | GET /shopper-bindings/{bindingId}/cart | cart.read@1 / B |
Authorized cart.setItemQuantity | POST /operations (kind=cart.item.quantity.set) | cart.write@1 / B, schema pending |
GET /products/{id}/base-price → 200, ETag: "price-r17":
{"data":{"productId":"42","amount":"19.9900","currency":"USD","revision":"price-r17","affectedStoreIds":["0"]},"meta":{"requestId":"req_demo","protocol":"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
{
"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
{
"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" }
}Повтори, конфлікти й невідомий результат
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.
Розширення покупця й кошика
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 з невірною схемою не є успіхом.
| HTTP | Stable code | Meaning / next step |
|---|---|---|
| 400 | INVALID_REQUEST | Malformed transport/JSON; fix request |
| 401 | AUTHENTICATION_FAILED | Invalid/expired/replayed signature; no effect |
| 403 | SCOPE_DENIED, CONNECTION_REVOKED, STORE_PAUSED, APPROVAL_REQUIRED | No new effect; restore authority explicitly |
| 404 | RESOURCE_NOT_FOUND, OPERATION_NOT_FOUND | Check identity; do not infer write outcome |
| 409 | REVISION_CONFLICT, IDEMPOTENCY_MISMATCH | No new write; inspect conflict |
| 413 | BODY_TOO_LARGE | Reduce request size |
| 422 | VALIDATION_FAILED, CAPABILITY_UNAVAILABLE | Fix data or disable tool |
| 429 | RATE_LIMITED | Respect Retry-After |
| 503 | TEMPORARILY_UNAVAILABLE | Read back operation before write retry |
{"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. Записи не оголошувати до проходження цих перевірок.
Джерела й пов’язані роботи
- ADR 002.
- STOR-16 · STOR-17.
- RFC 9421 — HTTP Message Signatures, RFC 9530 — Content-Digest: signing and digest foundations; the profile and timing limits above are Store Deputy proposals.
- RFC 9457 — Problem Details, RFC 9110 — HTTP semantics: error shape and HTTP behavior; application codes/states are our contract.
- RFC 8785 — JSON Canonicalization Scheme: canonical JSON for action hashes; shared PHP/Node fixtures remain required.