Bridge Protocol v1
ARCHIVE
Архивное исследование / черновик. Не является актуальным контрактом реализации. Смотрите REST 1.0 и актуальную концепцию.
ARCHIVE
Архивный исследовательский черновик. Не определяет актуальный протокол или требования совместимости. Для реализации используйте 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.
Запрос
{
"protocol": "1.0",
"id": "01J8Z6Q7T3M2K4X9V0B1C2D3E4",
"op": "catalog.get",
"params": { "product_ids": [42, 43] }
}Ответ
{
"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-Connection | connection_id |
X-SD-Timestamp | Секунды Unix |
X-SD-Nonce | Случайное значение, от 16 байтов, base64url |
X-SD-Signature | v1= + hex HMAC-SHA256 от v1\n{timestamp}\n{nonce}\n{sha256_hex(body)} |
Мост отклоняет запросы, время которых отличается от его часов больше чем на 300 секунд, или с nonce, встречавшимся за последние 10 минут. Подписи сравниваются за постоянное время. Бэкенд проверяет подпись ответа — именно так он отличает мост от страницы прокси.
Заголовок Authorization намеренно не используется: распространённые конфигурации CGI/FastCGI не передают его в PHP.
Подключение: соединить магазин может только наш бэкенд
Подключение должно быть невозможным для того, кто просто увидел адрес. Мост хранит набор публичных ключей подписи Store Deputy, каждый с идентификатором, и принимает запрос на подключение только если он подписан одним из них. Начальный набор поставляется с модулем; его можно обновить без обновления модуля (см. управление ключами).
- Владелец нажимает Подключить в модуле. Мост создаёт одноразовый
pairing_code(действует 10 минут, привязан к этому администратору) и перенаправляет на сайт аккаунтов Store Deputy с кодом, адресом моста и адресом возврата. - После входа владельца бэкенд вызывает
connection.pairна мосту, подписанный приватным ключом бэкенда (ECDSA P-256 через OpenSSL). Он передаёт код, новыйconnection_idи секрет. - Мост проверяет подпись и код, сохраняет учётные данные, уничтожает код и возвращает манифест
store.describe, подписанный HMAC новым секретом. Этот вызов также доказывает, что бэкенд может достучаться до магазина. Если он не удастся, владелец узнает об этом при настройке, а не во время первой задачи с ценами. - Бэкенд возвращает владельца в модуль. Адрес возврата должен быть на том же хосте, с которого началось подключение.
Секрет никогда не появляется в адресе, 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.pair | trust.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.list | catalog.read | Чтение |
catalog.search | catalog.read | Чтение |
catalog.get | catalog.read | Чтение |
price.apply | price.base.write | Запись |
operation.get | price.base.write | Чтение |
Просмотр заказов, запись остатков, спеццены и опции не входят в v1 (просмотр заказов отложен решением от 28 сентября 2026). Каждая из этих функций, если появится, будет новой возможностью в минорной версии.
store.describe
Возвращает манифест, по которому бэкенд решает, какие инструменты агента включены для этого магазина. Агенту никогда не предлагается инструмент, который магазин не заявил.
{
"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.
catalog.search
Ищет товары по идентификатору. Возвращает все совпадения и никогда не выбирает одно. Как разрешить дубликат, решает владелец, и это происходит в бэкенде.
{
"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. Бэкенд отправляет её только для одобренной версии задачи; агент не может вызвать её с произвольной ценой.
{
"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 позиций. Для каждой позиции по очереди мост:
- Возвращает сохранённый результат, если
operation_keyуже завершён в его журнале. Ничто не выполняется дважды. - Записывает ключ в журнал со статусом
started. - Выполняет одну условную команду:
UPDATE product SET price = :set, date_modified = NOW() WHERE product_id = :id AND price = :expected. - Если ни одна строка не изменилась, читает товар. Отсутствующий товар — это
not_found. Другая цена —conflictс текущим значением. - Читает цену обратно и сравнивает её с
set. - Записывает результат в журнал.
После пакета мост очищает кеш 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. Минорная версия только добавляет: новые операции, новые необязательные поля, новые возможности. Обе стороны игнорируют неизвестные поля. Мажорная версия может ломать совместимость, и мост может поддерживать несколько.- Бэкенд поддерживает текущую и предыдущую мажорные версии, так что владельцам не придётся обновлять модуль в день нашего релиза.
- У возможности своя версия. Она меняется, когда на платформе меняется смысл операции, а протокол остаётся тем же.
Как добавляется новая платформа или версия
- Реализовать операции на новой платформе, повторно используя переносимое ядро моста: конверт, подписи, хранилище nonce, журнал, манифест.
- Добавить образ магазина этой версии в тестовую матрицу.
- Пройти набор тестов на соответствие. До этого версия не поддерживается, как бы ни выглядел её код.
Предлагаемые сценарии проверки: чтение совпадает с базой данных; поиск по идентификатору возвращает все дубликаты; запись применяется и читается обратно; одновременное редактирование в админке даёт 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.