Один шов вместо трёх догадок
Проблема начинается не в синтаксисе. Команда называет один ответ «успехом», но 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 за переносимую информацию.
| Поле | Зачем оно названо | Когда остановиться |
|---|---|---|
| schemaVersion | связывает record и adapter | версия пуста или различается |
| value.tag | не даёт подменить вид значения coercion-ом | tag отсутствует или другой |
| error | переносит код и retry без object identity | нет named envelope |
| time | задаёт сравнимый интервал | нет ordered fixed ticks |
| mapping | показывает, сохранён ли shape | не exact |
Порядок сборки record
- Назвать одну операцию, а не домен целиком: в fixture это fixed-order-decision.
- Записать value как tagged fields; не отправлять «любое JSON».
- Выбрать единственную error semantics — named envelope — и назвать retry.
- Выбрать fixed logical ticks и записать оба конца интервала.
- Добавить по одному model label: php, javascript, d; требовать exact mapping.
- Запустить 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. Для другой задачи можно создать отдельный договор, но нельзя тихо ослабить этот.
Чек перед передачей
- Все три model label присутствуют по одному разу.
- Версия каждого adapter record совпадает с верхней версией.
- Tag значения и error semantics совпадают буквально.
- Время имеет fixed basis и два упорядоченных integer tick.
- Mapping у каждого adapter — exact; coercion даёт stop.
- Положительный результат говорит только о 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
| Status | Чего не хватает | Минимальная следующая правка |
|---|---|---|
| stop-incomplete-contract | schema, operation, tag или полный adapter set | назвать отсутствующее поле без default |
| stop-undetermined-time-boundary | единые ordered fixed ticks | заменить time record, не подставлять clock |
| stop-incomparable-adapter | exact сохранение 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-кода.