{ "index": 55, "slug": "editorial-2026-06-field-multi-runtime", "title": "Когда PHP, JavaScript и D говорят разное: как закрыть границу контракта", "excerpt": "Разные runtime не становятся одной системой от похожего JSON. Разберём наблюдаемый симптом, явный контракт, отрицательный путь и критерий, который отделяет проверяемую модель от заявления об интеграции.", "contentHtml": "

Запрос проходит через PHP, затем попадает в JavaScript и заканчивается обработкой на D. Пользователь видит ошибку без понятной причины, а оператор не может связать запись D с исходным запросом. Иногда все три части возвращают похожий JSON, но одна сторона считает ошибку исключением, другая — обычным значением, а третья записывает время в другой шкале. Внешне система работает. Внутри она уже потеряла общий смысл.

\n

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

\n

Тезис: общий смысл живёт на границе

\n

Три runtime можно соединить только через узкий, именованный контракт. Он должен назвать операцию, форму успешного значения, форму ошибки и основание времени. Каждый адаптер обязан сохранить эти поля без скрытого приведения. Если поле нельзя сопоставить, система должна остановиться и назвать причину. Молчаливое «примерно подходит» опаснее явного отказа.

\n

В этом материале проверяется модель границы. Учебный пример хранит запись в памяти JavaScript и читает её обычной функцией. Он не запускает PHP, JavaScript как отдельный процесс или D. Он не обращается к сети, диску, часам, телеметрии и сервисам. Поэтому его результат говорит только о внутренней сопоставимости записи. Это ограничение входит в смысл примера.

\n

Механизм: четыре поля, которые нельзя угадывать

\n

Сначала назовите операцию. Строка fixed-order-decision лучше, чем общий «обработчик заказа»: у неё есть конкретная граница. Затем задайте value tag. В примере это order-ready. Число 4200 получает единицу и валюту. Без tag и единицы потребитель может принять копейки за рубли или число лимита за сумму.

\n

Ошибка получает named envelope. В нём явно присутствуют semantics, code и retry. Значение code: null означает отсутствие кода внутри известного envelope. Отсутствующий сам envelope означает другую проблему. Эти два случая нельзя сливать в одну пустую строку.

\n

Время в изолированной модели задаётся ordered fixed logical ticks. Пара 100..108 показывает порядок и интервал из восьми условных шагов. Это не миллисекунды, не latency и не SLA. Если нужен production-замер, он требует отдельного источника времени, политики измерения и проверки среды.

\n
const contract = {\n  schemaVersion: 'fixed-boundary-1',\n  operation: 'fixed-order-decision',\n  value: { tag: 'order-ready', amountMinor: 4200, currency: 'RUB' },\n  error: { semantics: 'named-envelope', code: null, retry: 'not-requested' },\n  time: { basis: 'fixed-logical-ticks', opened: 100, closed: 108 }\n};\n\nconst adapters = ['php', 'javascript', 'd'].map((model) => ({\n  model,\n  contractVersion: 'fixed-boundary-1',\n  valueTag: 'order-ready',\n  errorSemantics: 'named-envelope',\n  timeBasis: 'fixed-logical-ticks',\n  mapping: 'exact'\n}));\n\nfunction check(record) {\n  if (record.schemaVersion !== 'fixed-boundary-1') {\n    return { status: 'stop-incomplete-contract', reason: 'schema-version-missing' };\n  }\n  if (record.adapters.some((item) => item.mapping !== 'exact')) {\n    return { status: 'stop-incomparable-adapter', reason: 'mapping-is-not-exact' };\n  }\n  return { status: 'synthetic-review-hand-off', observedEffect: 'none-observed' };\n}\n\nconsole.log(check({ ...contract, adapters }));
\n

Пример учебный. Он показывает форму проверки, а не совместимость библиотек. В нём имена php, javascript и d — значения поля model. Они не доказывают, что три языка обменялись данными. Положительный результат означает лишь: запись содержит названные поля, версии совпадают, а mapping не скрывает преобразование.

\n

Как распознать ложное совпадение

\n

Одинаковый текст ошибки не задаёт одинаковое действие. PHP может вернуть envelope, JavaScript — выбросить значение, а D — записать код в отдельное поле. Потребитель, который проверяет только сообщение, потеряет режим завершения. Сравнивайте не текст, а семантику: кто владеет ошибкой, можно ли повторить операцию и какие данные сохраняются.

\n

Одинаковое число времени тоже ничего не гарантирует. Один адаптер может передать логические шаги, другой — epoch seconds. Числа совпадут случайно, а вывод окажется ложным. Поэтому basis должна быть полем контракта и каждого адаптера. Пропущенный closed должен закрывать проверку, а не заменяться текущим временем.

\n
Симптомы на границе и проверяемое действие
СимптомПричинаПроверкаДействие
Ответы похожи, но retry ведёт себя по-разномуEnvelope смешан с thrown valueСравнить error semantics и наличие code/retryВыровнять envelope или вернуть stop
Время совпадает только на одном стендеРазные time basisПроверить basis и обе границы интервалаНазвать одну шкалу или прекратить сравнение
Сумма проходит проверку, но меняет порядок величиныTag, unit или currency угадываются по имениПроверить value tag, amountMinor и currencyДобавить явное поле и запретить inference
Адаптер «почти» совпадает с contractСкрытая coercion при mappingПотребовать mapping: exactОписать преобразование явно либо остановить hand-off
Нельзя связать запись D с запросом PHPНет общего correlation fieldПроверить, входит ли идентификатор в contractДобавить поле в новый контракт; не восстанавливать связь по времени
\n
\"Цикл
Схема показывает чтение одной записи в памяти. Стрелки не означают сетевое соединение, запуск runtime или измерение production.
\n

Отрицательный путь важнее happy path

\n

Проверка должна отказываться от удобного вывода. Возьмём запись, где у одного адаптера errorSemantics: 'thrown-value', а у контракта остаётся named-envelope. Такой набор нельзя объявить совместимым. Функция возвращает stop-incomparable-adapter и оставляет владельцу конкретное действие: выровнять семантику.

\n

Другой случай: time.basis равен wall-clock, а closed отсутствует. Здесь нельзя написать «обработка заняла неизвестное время» и нельзя вычислить значение из текущих часов. Проверка должна вернуть stop-undetermined-time-boundary. Она не спорит о точности. Она фиксирует отсутствие основания для сравнения.

\n

Третий случай — coercion. Если адаптер превратил строку в число, округлил сумму или заменил пустое значение значением по умолчанию, результат уже не exact mapping. Приведение может быть правильным в конкретной программе, но оно требует правила, единицы и теста. До этого момента оно скрывает смысл и закрывает hand-off.

\n

Порядок действий

\n
  1. Назовите одну операцию и версию схемы. Не начинайте с описания всех сервисов.
  2. Опишите value через tag и явные единицы: например, amountMinor и currency.
  3. Опишите error envelope целиком. Зафиксируйте code, retry и допустимое отсутствие кода.
  4. Выберите одну time basis и запишите ordered opened и closed. Не называйте ticks миллисекундами без источника.
  5. Добавьте по одной записи для PHP, JavaScript и D. Сверяйте каждый адаптер с contract.
  6. Запретите скрытое приведение. Для каждого преобразования добавьте именованное правило или верните stop.
  7. Прогоните положительный и отрицательные случаи через одну проверку. Сохраните status и nextAction без редакторской интерпретации.
  8. Передайте запись на следующий review только как synthetic hand-off. Реальную интеграцию проверяйте отдельным набором доказательств.
\n

Что можно утверждать после проверки

\n

Допустимая формулировка узкая: «фиксированная запись соответствует названным правилам и может перейти на synthetic review». Нельзя писать «PHP, JavaScript и D совместимы», «адаптер работает», «интеграция подтверждена» или «latency равна восьми». У модели нет runtime, транспорта, хоста, зависимостей, прав, пользовательских данных и production-метрик.

\n

Это не бюрократическая оговорка. Явная граница защищает решение от расширения смысла при копировании. Читатель видит, какой факт проверен, а какой ещё требует отдельного эксперимента. Если понадобится связать PHP-запрос и запись D, добавьте correlation id и проверьте его на реальном пути. Не выводите связь из одинакового времени, порядка строк или похожего JSON.

\n

Ограничения

\n

Модель не описывает ABI, сериализацию, nullable policy, иерархию классов, stack unwinding, сборку мусора, планировщик, retry конкретного клиента или схему регистрации сервисов. Эти свойства нельзя спрятать в поле details. Если свойство влияет на решение, назовите его отдельным полем и задайте проверку. Если назвать его нельзя, остановите границу.

\n

Модель также не заменяет контракт домена. Tag order-ready говорит о форме значения, но не доказывает, что бизнес действительно разрешает выдавать заказ. Доменный смысл проверяет владелец операции. Техническая проверка должна передать ему точное значение и не присваивать себе его решение.

\n

Проверяемый критерий готовности

\n

Граница готова к synthetic hand-off, если выполнены все условия: есть schema version и operation; value имеет tag и единицы; error содержит named semantics, code и retry; time имеет одну basis и целые ordered ticks; присутствуют все три model labels; каждый адаптер сохраняет version, tag, error semantics, time basis и exact mapping; отрицательные случаи возвращают именованные stop; результат содержит observedEffect: none-observed.

\n

Проверяемый результат можно повторить на той же записи и получить тот же status. Если status меняется от текущих часов, сети, окружения или неявной нормализации, это уже не изолированная проверка. Если положительный status требует доверия к словам о запущенном сервисе, он выходит за границу примера. В обоих случаях работу нужно остановить и уточнить новый scope.

\n

Проверяемые источники

\n" }