DarkRiDDeR20 мин

Три языка, один узкий контракт: граница без магии адаптеров

PHPJavaScriptDLang

Один шов вместо трёх догадок

Проблема начинается не в синтаксисе. Команда называет один ответ «успехом», но PHP, JavaScript и D понимают значение, ошибку и время через разные собственные модели. Цена такой неясности — не абстрактная несовместимость, а дорогое обсуждение после того, как один адаптер уже превратил отсутствие поля в ноль, другой сохранил исключение, а третий потерял основание времени.

Первый плохой ход — рисовать связь трёх runtime-ов и сразу искать реальную интеграцию. Это увеличивает стоимость ошибки: спор становится про окружение, логи и доступы, хотя вопрос ещё не сформулирован. Здесь выбирается более узкий объект: named fixed synthetic JS literal, который описывает только boundary contract и допускает только review hand-off.

Что именно является контрактом

Narrow boundary contract — это не «универсальный DTO» и не обещание, что любой язык автоматически его прочитает. В пакете это один объект с версией, именованной операцией, tagged value, error envelope, временной шкалой и тремя adapter records. Каждый элемент существует потому, что его отсутствие меняет смысл вывода. Вне объекта нет неявного default, локальной конфигурации и догадки рецензента.

Поле value.tag отделяет вид значения от его представления. amountMinor не объявлен денежным протоколом и не приводится к float. Это фиксированное число в учебном record. Поле error не хранит exception object: там named semantics, code и retry. Так boundary не импортирует чужую иерархию ошибок и не выдаёт local throw за переносимую информацию.

Минимальные поля fixed boundary contract
ПолеЗачем оно названоКогда остановиться
schemaVersionсвязывает record и adapterверсия пуста или различается
value.tagне даёт подменить вид значения coercion-омtag отсутствует или другой
errorпереносит код и retry без object identityнет named envelope
timeзадаёт сравнимый интервалнет ordered fixed ticks
mappingпоказывает, сохранён ли shapeне exact
Карта фиксированной границы: три языковые модели сходятся к одному named contract, затем к синтетическому review hand-off.
Рисунок не изображает сетевое соединение или работающие runtime. Он показывает только структуру in-memory record.

Порядок сборки record

  1. Назвать одну операцию, а не домен целиком: в fixture это fixed-order-decision.
  2. Записать value как tagged fields; не отправлять «любое JSON».
  3. Выбрать единственную error semantics — named envelope — и назвать retry.
  4. Выбрать fixed logical ticks и записать оба конца интервала.
  5. Добавить по одному model label: php, javascript, d; требовать exact mapping.
  6. Запустить review и принять только synthetic multi-runtime review hand-off либо stop reason.

Исполняемый пример

Ниже нет запуска PHP, D или отдельного JavaScript runtime. Модуль уже исполняется JavaScript-интерпретатором, а строки php, javascript и d — данные fixed literal. Поэтому результат отвечает только на вопрос о полноте модели, а не о совместимости программ.

import { createFixedMultiRuntimeRecord, reviewFixedBoundaryContract } from './upgrade-2026-06.mjs';

const record = createFixedMultiRuntimeRecord('named-contract-v1');
const handOff = reviewFixedBoundaryContract(record);

console.log({
  status: handOff.status,
  valueTag: handOff.valueTag,
  fixedElapsedTicks: handOff.fixedElapsedTicks,
  effect: handOff.effect,
});
// synthetic-multi-runtime-review-hand-off, order-ready, 8, no-system-change

У accepted path есть ровно одна положительная формулировка: record можно передать на synthetic multi-runtime review. Он не является integration certificate, deployment plan, результатом тестирования среды или подтверждением того, что adapters когда-либо связывались. Поле effect: no-system-change специально остаётся рядом с результатом, чтобы код не расширил текстовое обещание.

Где граница намеренно жёсткая

Один contract record не обязан сохранять все особенности каждой модели. Он не переносит nullable semantics, class hierarchy, stack trace, GC, scheduler и представление времени в конкретной библиотеке. Это не пробел для последующего «умного адаптера». Это запрет на скрытый перенос. Если свойство необходимо для решения, оно получает поле и named rule; если его нельзя назвать, review закрывается.

Ровно поэтому error semantics не равны строке сообщения. Сообщение может быть полезно человеку, но не задаёт сравнимого действия. Code и retry здесь также не претендуют на общий стандарт: они лишь делают фиксированный record проверяемым. Новая ошибка потребует новой зафиксированной fixture, а не добавления catch-all поля.

Контрпримеры полезнее happy path

Пустая схема возвращает stop-incomplete-contract, а не частичный ответ. Модель с thrown-value в одном adapter record и named-envelope в двух других не «почти готова»: она возвращает stop-incomparable-adapter. Эти stop reasons описывают недостаток record, не дефект PHP, JavaScript или D. Такой язык экономит время: владельцу понятно, что нужно назвать, а не что нужно срочно починить в неизвестной системе.

Неопределённое время закрывается stop-undetermined-time-boundary. Wall-clock и пустой конец интервала нельзя превратить в duration задним числом. Этот отказ не говорит, что часы плохие. Он говорит, что выбранный synthetic вопрос требует fixed logical ticks. Для другой задачи можно создать отдельный договор, но нельзя тихо ослабить этот.

Чек перед передачей

  1. Все три model label присутствуют по одному разу.
  2. Версия каждого adapter record совпадает с верхней версией.
  3. Tag значения и error semantics совпадают буквально.
  4. Время имеет fixed basis и два упорядоченных integer tick.
  5. Mapping у каждого adapter — exact; coercion даёт stop.
  6. Положительный результат говорит только о review hand-off.

Как не расширить контракт при первой же правке

Самая частая деградация начинается с полезного на вид поля metadata. Его добавляют, чтобы не выпускать новую версию, а затем в нём оказываются локальный тип, текст исключения, timezone или флаг конкретной библиотеки. Contract перестаёт быть narrow: значимая информация снова живёт без schema и без правила mapping. Правильная реакция на новый факт обратная: сначала решить, участвует ли он в review-вопросе. Если нет — не добавлять. Если да — назвать поле, version change и fail-closed случай.

Вторая деградация — выдавать нулевое значение за отсутствие данных. Например, amountMinor: 0 в этом literal остаётся числом, а не сигналом ошибки, потому что tag уже сказал вид value. Пустой amount не заменяется нулём; он требует другого tag или stop. Такой запрет кажется формальным, но он защищает обсуждение: рецензент не должен выяснять, означает ли ноль бесплатную операцию, отсутствие конверсии или непрочитанное поле.

Третья деградация — считать модельный label адресом реализации. Поле model: php не несёт версию интерпретатора, process id, package list или transport. Оно лишь требует, чтобы в fixed contract было три заранее названных perspective records. Если будущая задача зависит от конкретной версии, её нельзя спрятать в label. Нужен другой data contract и отдельная historical source pin, иначе сравнением управляет неявное знание автора.

Разбор одного accepted record

В named-contract-v1 операция названа до adapter list. Это важно: adapters не голосуют за смысл операции, а сверяются с уже объявленным объектом. Tag order-ready не описывает состояние реального заказа; он всего лишь дискретная метка fixed input. Amount и currency показывают, что tag может нести поля, но review не проверяет их экономический смысл, диапазон и format. Его задача уже уже: увидеть одинаковый shape во всех трёх records.

Error envelope в accepted record содержит code: null. Null здесь не превращается в «ошибок в реальной системе нет». Он означает, что конкретный synthetic value хранит отсутствие named error code. Retry not-requested также не запускает повтор и не задаёт backoff. Два поля нужны только потому, что иначе один adapter мог бы молча добавить retry rule, а другой — интерпретировать null как неизвестное состояние.

Ticks 100 и 108 не являются timestamp. Их нельзя сопоставить с календарём и нельзя вычесть из них метрику. У них один полезный смысл: fixed record содержит порядок и разность восемь. Это удерживает автора от ложного вывода о скорости. Если reader хочет говорить о времени исполнения, ему придётся сформулировать другой input и разрешить другой evidence; старая карточка не сможет случайно стать основанием.

Решение владельца по каждому stop

Стоп-сигнал и минимальная правка literal
StatusЧего не хватаетМинимальная следующая правка
stop-incomplete-contractschema, operation, tag или полный adapter setназвать отсутствующее поле без default
stop-undetermined-time-boundaryединые ordered fixed ticksзаменить time record, не подставлять clock
stop-incomparable-adapterexact сохранение contract shapeубрать coercion или завести явный новый вариант

Владелец не обязан немедленно исправлять stop. Иногда точный ответ — оставить record закрытым, потому что необходимого факта нет. Это лучше, чем вводить вымышленный mapping ради зелёного fixture. Fail-closed protocol не измеряет продуктивность команды количеством accepted results. Он измеряет качество тем, что не даёт невнятному input стать переносимым утверждением.

Ограничения и следующий шаг

Этот материал не измеряет latency, не вызывает файловую систему, не читает clock, не собирает telemetry, не строит trace и не содержит PII. Он не проверяет PHP, JavaScript или D и не утверждает, что они реально связаны. Даже слова adapter и integration здесь обозначают поля synthetic record, а не сетевой слой.

Следующий безопасный шаг — добавить один новый named fail-closed literal: например, value tag без явного mapping rule. Затем надо повторить review и убедиться, что он останавливается точной причиной. Только после отдельного решения о реальном контуре можно обсуждать другой вид проверки; данный пакет такого решения не принимает.

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

  • PHP source: annotated tag php-8.4.0 — версия: annotated tag php-8.4.0, tag object 501fc4ce253940b58a8744b1083951327e07f00e, 19 November 2024, immutable commit pin. Фиксирует конкретный исходный срез PHP 8.4.0; он не описывает совместимость с другими языками. Граница: В статье не запускается PHP и не делается вывод о его поведении.
  • ECMA-262, 16th edition: ECMAScript 2025 Language Specification — версия: 16th edition, June 2025, edition-specific publication. Определяет ECMAScript language types и completion semantics для edition 2025. Граница: JS-объекты в примере — только носители synthetic records.
  • D compiler: annotated tag v2.111.0 — версия: annotated tag v2.111.0, tag object 6ae81e26cedc8f792d0074186e38974542f1a3cc, 31 March 2025; release 1 April 2025, immutable commit pin. Фиксирует конкретный D compiler release line 2.111.0. Граница: Метка d в literаl не означает исполнение D-кода.