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

Bridge Protocol v1 ​

ARCHIVE

Архивное исследование / черновик. Не является актуальным контрактом реализации. Смотрите REST 1.0 и актуальную концепцию.

ARCHIVE

Архивный исследовательский черновик. Не определяет актуальный протокол или требования совместимости. Для реализации используйте REST 1.0.

REST 1.0

Принятая архитектура · 1 октября 2026, вариант 2

Agentfy содержит harness; API Store Deputy предоставляет единый MCP; OpenCart-модуль — защищённый REST/JSON API. Store Deputy проверяет подключения, контекст сотрудника/покупателя, права, согласования и аудит, не создавая второй harness. Connect → вход/регистрация → безопасное подключение → автоматическое создание агента в Agentfy с MCP Store Deputy → возврат в модуль. В app: команды, агенты, аккаунт, биллинг и чат с каждым агентом.

Это заменяет предыдущее решение того же дня про MCP непосредственно в модуле. Код и прототипы ниже ещё не реализуют новую интеграцию. Сессии покупателей и настоящая корзина OpenCart имеют отдельную проверяемую привязку; cookie остаётся в магазине. См. решение о едином MCP: границы, этапы и оставшаяся работа над контрактом.

Предложен преемник · 2 октября 2026. Контракт шлюза v2 — черновик REST-контракта, который заменит этот RPC-протокол после согласования. До тех пор код говорит на v1.

Черновик спецификации · 28 сентября 2026 · частично построено, только на тестовых магазинах. Мост для OpenCart 3 отвечает на connection.pair, connection.revoke, store.describe, catalog.search и catalog.get (мост 0.4.0, 30 сентября 2026), а технический эксперимент 29 сентября 2026 проверил price.apply (см. что делает запись). Всё это работало только на двух локальных тестовых магазинах, на реальном магазине — никогда. Решения, отмеченные как принятые, принял основатель; всё остальное — предложения.

Store Deputy общается с каждым поддерживаемым магазином на одном собственном языке; для каждой платформы устанавливается небольшой мост, который его переводит. Чтобы добавить версию OpenCart, форк или другую платформу, достаточно написать ещё один мост, проходящий те же тесты. Бэкенд, агент и правила одобрения остаются неизменными.

Владелец магазина никогда не видит этот протокол — он видит его последствия. Цена, которую изменили уже после того, как он одобрил список, будет пропущена, а не перезаписана. Задача, прерванная сбоем сети, никогда не применится дважды. Кнопка Пауза в собственной админпанели останавливает агента независимо от того, что происходит на нашей стороне.

Принятые границы ​

РешениеПоследствие для протокола
MCP работает в бэкенде Store DeputyАгент никогда не обращается к магазину. К мосту обращается только бэкенд, после проверки одобрения
Первая платформа: OpenCart 3.0.3.5 и новееКод моста рассчитан на PHP 7.3 и Twig 2/3; см. заметки по OpenCart 3
Только прямые запросыБэкенд обращается к магазину по HTTPS. Магазин должен быть доступен из интернета; это проверяется при подключении
Записывается только базовая ценаОдна операция записи. Спеццены, скидки за количество, цены опций и остатки читаются или игнорируются, но никогда не записываются

Почему мост, а не собственный API каждой платформы: контракт выполнения требует трёх гарантий, которых нативные API магазинов вместе не дают. Цену можно записать только тогда, когда она всё ещё равна значению, одобренному владельцем. Повторный запрос не может применить изменение дважды. Запись должна быть прочитана обратно. Собственный код в магазине нам нужен в любом случае, а один общий протокол позволяет реализовать коннектор в бэкенде только один раз.

Agent ── MCP tools ──▶ Store Deputy backend ── approvals, audit, jobs
                               │
                               │  Bridge Protocol v1 (HTTPS, signed)
                               ▼
                      Bridge in the shop  ──▶  OpenCart 3.0.3.5+ database

Транспорт ​

  • Одна точка входа на магазин, которую мост сообщает при подключении. Только POST, Content-Type: application/json, UTF-8, тело запроса не больше 1 МБ.
  • HTTPS обязателен. Мост отклоняет обычный HTTP, если в его конфигурации не включён режим разработки.
  • Стиль RPC, а не REST. Название операции передаётся в теле. Один адрес легко разместить на любой платформе (маршрут OpenCart, маршрут WordPress REST), а всё тело покрывается одной подписью.
  • Статус 200 означает, что ответил мост — с результатом или с ошибкой. Любой другой статус или 200 без действительной подписи ответа означает, что ответил кто-то между нами и мостом: файрвол, проверка CDN, страница обслуживания. Бэкенд показывает это как «магазин недоступен», а не как ошибку магазина. Решает подпись, а не статус: на тестовых магазинах фатальная ошибка OpenCart тоже возвращается со статусом 200.

Запрос ​

json
{
  "protocol": "1.0",
  "id": "01J8Z6Q7T3M2K4X9V0B1C2D3E4",
  "op": "catalog.get",
  "params": { "product_ids": [42, 43] }
}

Ответ ​

json
{
  "protocol": "1.0",
  "id": "01J8Z6Q7T3M2K4X9V0B1C2D3E4",
  "ok": true,
  "result": { "products": [] }
}

При ошибке ok равно false, а вместо result возвращается "error": { "code": "…", "message": "…", "retryable": false, "details": {} }. id — это ULID, который выбирает бэкенд. Он уникален для каждого запроса и возвращается в ответе.

Аутентификация ​

Ежедневные запросы: HMAC в обоих направлениях ​

У каждого подключения есть connection_id и 32-байтовый секрет, известные бэкенду и мосту. Каждый запрос и каждый ответ содержат:

ЗаголовокЗначение
X-SD-Connectionconnection_id
X-SD-TimestampСекунды Unix
X-SD-NonceСлучайное значение, от 16 байтов, base64url
X-SD-Signaturev1= + hex HMAC-SHA256 от v1\n{timestamp}\n{nonce}\n{sha256_hex(body)}

Мост отклоняет запросы, время которых отличается от его часов больше чем на 300 секунд, или с nonce, встречавшимся за последние 10 минут. Подписи сравниваются за постоянное время. Бэкенд проверяет подпись ответа — именно так он отличает мост от страницы прокси.

Заголовок Authorization намеренно не используется: распространённые конфигурации CGI/FastCGI не передают его в PHP.

Подключение: соединить магазин может только наш бэкенд ​

Подключение должно быть невозможным для того, кто просто увидел адрес. Мост хранит набор публичных ключей подписи Store Deputy, каждый с идентификатором, и принимает запрос на подключение только если он подписан одним из них. Начальный набор поставляется с модулем; его можно обновить без обновления модуля (см. управление ключами).

  1. Владелец нажимает Подключить в модуле. Мост создаёт одноразовый pairing_code (действует 10 минут, привязан к этому администратору) и перенаправляет на сайт аккаунтов Store Deputy с кодом, адресом моста и адресом возврата.
  2. После входа владельца бэкенд вызывает connection.pair на мосту, подписанный приватным ключом бэкенда (ECDSA P-256 через OpenSSL). Он передаёт код, новый connection_id и секрет.
  3. Мост проверяет подпись и код, сохраняет учётные данные, уничтожает код и возвращает манифест store.describe, подписанный HMAC новым секретом. Этот вызов также доказывает, что бэкенд может достучаться до магазина. Если он не удастся, владелец узнает об этом при настройке, а не во время первой задачи с ценами.
  4. Бэкенд возвращает владельца в модуль. Адрес возврата должен быть на том же хосте, с которого началось подключение.

Секрет никогда не появляется в адресе, HTML-странице или журнале.

Как реализовано в итерации 3 (29 сентября 2026) на локальных тестовых магазинах. Запрос подключения содержит X-SD-Key-Id, X-SD-Timestamp, X-SD-Nonce и X-SD-Pairing-Signature: v1=<base64url DER ECDSA-SHA256> над той же канонической строкой, что и HMAC. Параметры: pairing_code, connection_id, secret и display (название команды и email подключающего — показываются на странице модуля). Мост подписывает ответ предложенным секретом даже при отказе, поэтому Store Deputy всегда видит, что ответил именно мост. Код хранится только как хеш SHA-256 и расходуется условным обновлением, поэтому два запроса с одним кодом не могут оба пройти. Адрес возврата содержит user_token админки OpenCart; сайт не передаёт referrer, а Store Deputy его не хранит.

Управление ключами ​

Принято: ключи должны заменяться через интерфейс, а не только обновлением модуля. Описанный ниже механизм — предложение, ещё не реализованное: в этой версии модуль доверяет одному ключу бэкенда, который вписывается в модуль при сборке (решение от 29 сентября 2026; ротация — отдельной итерацией).

Есть два вида ключей, и каждый можно заменить отдельно.

КлючДля чегоКак заменяется
Ключи подключения бэкенда (публичные, в мосту)Проверка connection.pairtrust.update из бэкенда или вручную в модуле
Секрет подключения (общий)Подпись ежедневных запросов и ответовconnection.rotate_secret из бэкенда или Переподключить в модуле

Два уровня ключей бэкенда. Корневой ключ хранится офлайн и подписывает только наборы ключей. Ключи подключения бэкенд использует каждый день. Мост принимает новый набор ключей подключения, только если он подписан корневым ключом, которому мост уже доверяет. Поэтому украденный ключ подключения можно вывести из обращения, и им нельзя установить ключи злоумышленника. Сами корневые ключи меняются только набором, подписанным текущим корневым ключом, или обновлением модуля.

Параметры trust.update: keyset (идентификаторы ключей, публичные ключи, not_before, not_after), keyset_version (должна быть больше сохранённой, чтобы старый набор нельзя было отправить повторно) и root_signature. Мост хранит предыдущий набор до not_before нового, поэтому нет момента, когда подключение не работает.

connection.rotate_secret подписывается HMAC текущим секретом и содержит новый. В течение 10 минут принимаются оба секрета, так что запросы, уже находящиеся в пути, завершатся успешно. После этого действует только новый.

В модуле вкладка «Подключение» показывает идентификатор подключения, отпечатки и идентификаторы доверенных ключей, версию набора ключей и время последнего изменения каждого ключа. Там доступны три действия:

  • Переподключить — повторно запускает подключение и выдаёт новый секрет.
  • Импортировать набор ключей — принимает подписанный файл набора ключей, опубликованный Store Deputy, например когда бэкенд не может достучаться до магазина. Подпись проверяется так же, как для trust.update.
  • Сбросить к ключам модуля — восстанавливает ключи, поставлявшиеся с установленной версией модуля, и отключает магазин.

Каждое изменение ключей записывается в журнал операций с указанием, кто его сделал: бэкенд или администратор в OpenCart.

Отключение и пауза ​

  • Отключение (в модуле или connection.revoke из бэкенда) удаляет секрет. Все последующие запросы завершаются ошибкой not_paired.
  • Пауза — собственный переключатель владельца в админке OpenCart. Во время паузы мост отвечает на все операции, кроме store.describe и connection.revoke, ошибкой paused: отключение только уменьшает доступ, поэтому работает всегда. Это не зависит от того, правильно ли ведёт себя бэкенд.

Операции ​

ОперацияВозможностьТип
connection.pair—Настройка, асимметричная подпись
connection.revoke—Настройка
connection.rotate_secret—Настройка
trust.update—Настройка, подпись корневым ключом
store.describe—Чтение
catalog.listcatalog.readЧтение
catalog.searchcatalog.readЧтение
catalog.getcatalog.readЧтение
price.applyprice.base.writeЗапись
operation.getprice.base.writeЧтение

Просмотр заказов, запись остатков, спеццены и опции не входят в v1 (просмотр заказов отложен решением от 28 сентября 2026). Каждая из этих функций, если появится, будет новой возможностью в минорной версии.

store.describe ​

Возвращает манифест, по которому бэкенд решает, какие инструменты агента включены для этого магазина. Агенту никогда не предлагается инструмент, который магазин не заявил.

json
{
  "bridge": { "version": "1.0.0", "protocols": ["1.0"] },
  "platform": {
    "name": "opencart",
    "reported_version": "3.0.3.8",
    "detected_version": "3.0.3.9",
    "fork": null,
    "php": "7.4.33",
    "db": "MariaDB 10.6"
  },
  "store": {
    "stores": [{ "store_id": 0, "name": "Example Store", "url": "https://shop.example.com/" }],
    "default_currency": "EUR",
    "prices_entered_without_tax": true,
    "display_prices_with_tax": true,
    "admin_language": "en-gb",
    "catalog_collation": "utf8_general_ci",
    "timezone": "Europe/Kyiv"
  },
  "limits": { "max_ids_per_read": 500, "max_items_per_write": 50, "time_budget_ms": 20000 },
  "capabilities": [
    { "name": "catalog.read", "version": 1 },
    { "name": "price.base.write", "version": 1 }
  ],
  "warnings": [
    { "code": "product_edit_listener", "details": { "event_code": "example_feed", "trigger": "admin/model/catalog/product/editProduct/after" } }
  ],
  "paused": false
}

Это иллюстративный пример, а не ответ реального магазина.

  • detected_version определяется по признакам в коде, а не только по константе VERSION. Релиз 3.0.3.9 до сих пор объявляет себя как 3.0.3.8.
  • warnings перечисляет то, что может вести себя иначе после нашей записи. Например, расширения, подписанные на события редактирования товара, и модификации OCMOD, меняющие модель товара; см. почему.
  • Несколько витрин стоит показать предупреждением в таблице проверки. В OpenCart базовая цена общая для всех витрин одной установки.

catalog.list ​

Постранично перебирает товары по возрастанию product_id, чтобы бэкенд построил индекс для сопоставления. Параметры: after_id (по умолчанию 0), limit (≤ max_ids_per_read), необязательный modified_since (ISO 8601). Возвращает products (в том же формате, что и catalog.get) и next_after_id или null.

Ищет товары по идентификатору. Возвращает все совпадения и никогда не выбирает одно. Как разрешить дубликат, решает владелец, и это происходит в бэкенде.

json
{
  "identifiers": [
    { "field": "sku", "value": "AB-100" },
    { "field": "ean", "value": "4006381333931" }
  ]
}

field — одно из product_id, model, sku, upc, ean, jan, isbn, mpn. Результат сохраняет порядок входных данных: [{ "input": …, "product_ids": [ … ] }]. Сравнение использует колляцию базы данных. В стандартной установке OpenCart это значит, что регистр не учитывается, а пробелы в конце игнорируются. Манифест сообщает колляцию, чтобы бэкенд мог объяснить неожиданное совпадение.

catalog.get ​

Параметры: product_ids (≤ max_ids_per_read). Для каждого товара возвращает:

ПолеПримечания
product_id, model, sku, upc, ean, jan, isbn, mpnЗначения без обработки
nameНа языке админки
status, quantity, store_ids, date_modifiedКонтекст только для чтения
priceДесятичная строка, 4 знака, напр. "19.9900", в валюте по умолчанию, без налога
tax_class_idЦена хранится без налога; налог добавляется при показе
active_specialsДействующие сейчас спеццены: группа покупателей, цена, даты, приоритет. Только чтение
has_discounts, has_option_pricesЕсть ли другие механизмы ценообразования

Суммы всегда передаются десятичными строками, никогда не числами JSON. Числа с плавающей запятой для денег недопустимы.

ID, для которых товара нет, возвращаются в missing, а не вызывают ошибку запроса.

active_specials нужно, чтобы таблица проверки могла предупредить владельца. Когда действует спеццена, покупатели видят её, а не новую базовую цену.

price.apply ​

Единственная операция записи в v1. Бэкенд отправляет её только для одобренной версии задачи; агент не может вызвать её с произвольной ценой.

json
{
  "job_id": "job_01J8Z7…",
  "approval_ref": "apr_01J8Z7…",
  "items": [
    {
      "operation_key": "opk_01J8Z7…",
      "product_id": 42,
      "expected": { "price": "19.9900" },
      "set": { "price": "21.5000" }
    }
  ]
}

Не больше max_items_per_write позиций. Для каждой позиции по очереди мост:

  1. Возвращает сохранённый результат, если operation_key уже завершён в его журнале. Ничто не выполняется дважды.
  2. Записывает ключ в журнал со статусом started.
  3. Выполняет одну условную команду: UPDATE product SET price = :set, date_modified = NOW() WHERE product_id = :id AND price = :expected.
  4. Если ни одна строка не изменилась, читает товар. Отсутствующий товар — это not_found. Другая цена — conflict с текущим значением.
  5. Читает цену обратно и сравнивает её с set.
  6. Записывает результат в журнал.

После пакета мост очищает кеш product в OpenCart, как это делает админка после сохранения товара. Иначе кешированные списки, например «Бестселлеры», будут показывать старую цену.

Результаты позиций:

РезультатЗначениеДействие бэкенда
appliedЗаписано и прочитано обратно как setГотово
conflictЦена не равнялась expected; ничего не записаноПропустить; новое предложение требует нового одобрения
not_foundТовара больше не существуетПропустить и сообщить
rejectedНекорректная позиция: неверное десятичное число, отрицательная цена, expected = setИсправить задачу; повторно не отправляется
not_attemptedВремя исчерпано до этой позицииОтправить повторно с тем же operation_key
failedОшибка базы данных; ничего не подтвержденоПроверить через operation.get, затем решить

Пакет не атомарный. Таблицы OpenCart 3 используют MyISAM без транзакций, поэтому у каждой позиции свой результат. Мост перестаёт начинать новые позиции, когда исчерпан time_budget_ms. Виртуальный хостинг часто обрывает PHP-запрос через 30 секунд, а наполовину завершённый запрос без ответа — худший случай.

operation.get ​

Параметры: operation_keys (≤ 500). Возвращает запись журнала для каждого ключа или unknown. После тайм-аута бэкенд вызывает эту операцию, а не отправляет запрос повторно вслепую:

  • unknown — запрос не дошёл до моста; можно безопасно отправить повторно с тем же ключом.
  • Завершённый результат — зафиксировать его; повторно не отправлять.
  • started без результата (запрос оборвался посреди позиции) — отправить повторно с тем же ключом. Это безопасно, потому что запись условная: если она уже применена, цена больше не равна expected. Позиция вернётся как конфликт, бэкенд увидит, что текущее значение равно set, то есть изменение применено, и сверит состояние.

Что запись делает и чего не делает ​

Мост не вызывает editProduct из OpenCart. Эта функция удаляет и заново вставляет спеццены, скидки, опции, изображения и описания товара из полной отправки формы и не имеет условия по старой цене. Использовать её для изменения одного числа — значит рисковать всем остальным в товаре и потерять гарантию от конфликтов.

Другим расширениям всё равно нужно узнать, что цена изменилась: фидам маркетплейсов, поисковым индексам, синхронизации с ERP. В OpenCart 3 они узнают об этом одним из трёх способов, и каждый обрабатывается по-своему.

Как расширение замечает изменениеПримерУвидит ли нашу запись?
Читает базу данных во время работы или ищет более новый date_modifiedФид, собираемый по запросу или по cron; выгрузка изменённых товаровДа. Запись обновляет date_modified, кеш очищается
Подписано на событие admin/model/catalog/product/editProduct/afterМодуль, отправляющий каждый сохранённый товар на маркетплейсДа на тестовых магазинах; мост сам генерирует событие, см. ниже
Модификация OCMOD или vQmod меняет модель или контроллер товараДополнительный код внутри editProductНет. Этот код выполняется только внутри editProduct

Предложение: генерировать событие «после сохранения» после записи. Когда цена применена и прочитана обратно, мост вызывает model/catalog/product/editProduct/after в контексте админки OpenCart с теми же аргументами, что и обычное сохранение: ID товара и полные данные товара, собранные собственными методами модели админки так, как их загружает форма товара. Для слушателя это выглядит так, будто владелец нажал Сохранить. Событие генерируется один раз для каждой применённой позиции и никогда — для conflict или not_found.

Результат эксперимента, 29 сентября 2026. На локальных тестовых магазинах с OpenCart 3.0.3.5 (PHP 7.3) и 3.0.5.1 (PHP 8.1, с переименованным каталогом админки) тестовое расширение, слушающее сохранение товара, получило ровно одно событие на каждую применённую цену — с описаниями, витринами, спецценами, скидками и опциями товара. Эти связанные записи после записи остались неизменными байт в байт. Конфликтная запись не сгенерировала ничего. На реальном магазине это ещё не проверяли.

Ограничения этого подхода:

  • Слушатели «перед сохранением» не вызываются. Они работают до записи и могут изменить или заменить её. Наша запись уже выполнена, и она условная.
  • Поля, которые модификация добавила в форму товара, стандартные методы не возвращают, поэтому слушатель может увидеть их пустыми. Мост сообщает о таких модификациях в warnings.
  • Нужен контекст админки. Слушателей для триггеров admin/ загружает только приложение админки. Оно отправляет каждый запрос без сессии администратора на страницу входа, а приложение витрины не может загрузить модель товара админки, потому что оба класса называются ModelCatalogProduct. Поэтому мосту нужна собственная точка входа, которая запускает приложение админки с нашей проверкой подписи вместо входа. Если на реальных магазинах это окажется ненадёжным, запасной вариант — маршрут на стороне витрины без уведомления событиями.
  • Точка входа должна заменять и маршрутизатор. Стандартный маршрутизатор OpenCart выполняет тот контроллер админки, который назван в ?route=. Без входа это позволило бы кому угодно выполнить любое действие админки через мост. Маршрутизатор моста выполняет только мост. В контрольном прогоне на тестовом магазине возврат стандартного маршрутизатора привёл к падению соответствующего сценария, как и должно быть.

Мост перечисляет обнаруженных слушателей событий и связанные с товаром модификации в warnings, так что владелец ещё до подключения знает, какие расширения увидят изменения, а какие нет.

Ошибки ​

КодМожно повторитьКогда
signature_invalidнетНеверная или отсутствующая подпись
timestamp_skewнетОтклонение от часов моста больше 300 с
replayedнетNonce уже использовался
not_pairedнетУчётных данных нет или они отозваны
pausedнетВладелец поставил агента на паузу в админпанели
unsupported_protocolнетМажорная версия не поддерживается; details.supported перечисляет версии
unknown_opнетОперация не реализована в этом мосту
capability_unavailableнетРеализовано, но недоступно в этом магазине
invalid_paramsнетОшибка проверки; details называет поле
limit_exceededнетСлишком много ID или позиций либо слишком большое тело
insecure_transportнетОбычный HTTP к релизной сборке модуля
unknown_keyнетconnection.pair подписан ключом, которому модуль не доверяет
pairing_code_invalidнетТакого кода подключения нет
pairing_code_usedнетКод уже использован
pairing_code_expiredнетКоду больше 10 минут
internalдаНеожиданный сбой моста; message никогда не содержит секретов или SQL

Версионирование ​

  • protocol имеет формат major.minor. Минорная версия только добавляет: новые операции, новые необязательные поля, новые возможности. Обе стороны игнорируют неизвестные поля. Мажорная версия может ломать совместимость, и мост может поддерживать несколько.
  • Бэкенд поддерживает текущую и предыдущую мажорные версии, так что владельцам не придётся обновлять модуль в день нашего релиза.
  • У возможности своя версия. Она меняется, когда на платформе меняется смысл операции, а протокол остаётся тем же.

Как добавляется новая платформа или версия ​

  1. Реализовать операции на новой платформе, повторно используя переносимое ядро моста: конверт, подписи, хранилище nonce, журнал, манифест.
  2. Добавить образ магазина этой версии в тестовую матрицу.
  3. Пройти набор тестов на соответствие. До этого версия не поддерживается, как бы ни выглядел её код.

Предлагаемые сценарии проверки: чтение совпадает с базой данных; поиск по идентификатору возвращает все дубликаты; запись применяется и читается обратно; одновременное редактирование в админке даёт conflict; повторный operation_key не записывает второй раз; запрос, оборвавшийся посреди позиции, сверяется через operation.get; исчерпанный лимит времени даёт not_attempted и чистую повторную отправку; неверная подпись, старое время, повторный nonce, отозванный секрет и пауза отклоняются; учётные данные другого подключения отклоняются; файл поставщика с инструкциями не влияет на действия моста.

Заметки по реализации для OpenCart 3 ​

Эти заметки получены из сравнения тегов релизов OpenCart. На работающем магазине их не проверяли.

  • Собственная точка входа, запускающая приложение админки (сработала в эксперименте на обоих тестовых магазинах). Установщик расширений OpenCart не пишет в корень магазина, поэтому архив кладёт точку входа в system/library/storedeputy/, а шаг Install модуля копирует её в корень, вписывая название каталога админки; Uninstall её удаляет. Если корень закрыт на запись, страница модуля объясняет, какой файл загрузить вручную. Точка входа — с проверкой подписи вместо входа OpenCart; см. что делает запись. Магазины часто переименовывают каталог admin, поэтому его расположение фиксируется при установке. Запасной вариант — маршрут на стороне витрины, который не может генерировать события админки.
  • Собственные таблицы: журнал операций (уникальный operation_key), который по решению хранится бессрочно, и хранилище nonce. Nonce нужны только для 10-минутного окна защиты от повторов и удаляются после него. Учётные данные и доверенный набор ключей хранятся в настройках OpenCart. Любой, у кого есть доступ к базе данных, может их прочитать — это касается секретов любого расширения OpenCart.
  • Минимальный синтаксис PHP 7.3: без стрелочных функций, типизированных свойств, match и именованных аргументов. 3.0.5.x требует PHP 8.1, поэтому код должен корректно работать и на 8.x.
  • Twig 2 и 3: 3.0.3.5–3.0.3.8 поставляются с Twig 2.13, 3.0.3.9 и новее — с Twig 3. Шаблоны админки используют только синтаксис, допустимый в обеих версиях. Например, без {% spaceless %} и без for … if.
  • Колляция и кодировка отличаются в старых установках (utf8) и новых 3.0.5.x (utf8mb4). Таблицы моста используют ту же кодировку, что и таблица товаров.

Открытые вопросы ​

  • Будет ли точка входа, сработавшая на тестовых магазинах, работать и на реальных магазинах с расширениями безопасности и модификациями OCMOD?
  • Необязательный список разрешённых IP-адресов нашего бэкенда, когда будет выбран хостинг.
  • Какие форки поддерживать — решают только реальные пилотные магазины.

См. также: полномочия и восстановление, план реализации, модуль OpenCart.