Files
progcode/editorial/agent-rewrites/057.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
17 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 57,
"slug": "editorial-2026-06-practice-multi-runtime",
"title": "PHP, JavaScript и D: как удержать общий контракт на границе runtime",
"excerpt": "Когда один ответ проходит через PHP, JavaScript и D, похожие поля ещё не означают одинаковый смысл. Разбираем узкий контракт, fail-closed проверку и границу между учебной моделью и реальной интеграцией.",
"contentHtml": "<p>Симптом обычно выглядит безобидно: PHP возвращает объект заказа, JavaScript показывает его как готовый, а D-обработчик принимает тот же пакет после адаптации. В логах остаются одинаковые поля, но в редком случае одно отсутствие превращается в <code>0</code>, другая ветка сохраняет исключение, а третья считает время по другой шкале. Ошибка обнаруживается уже после передачи данных. Цена — неверное решение, повторная обработка или часы разбора, потому что команда спорит о runtime вместо формы сообщения.</p>\n<p>Тезис простой: общий контракт нужно проектировать на границе задачи, а не выводить из сходства языков. Для учебной проверки достаточно одного объекта в памяти. В нём надо явно назвать операцию, вид значения, семантику ошибки, шкалу времени и правила адаптеров. Если хотя бы одно поле нельзя сравнить, проверка должна остановиться. Такой результат подтверждает только внутреннюю согласованность модели. Он не подтверждает работу PHP, JavaScript, D или production-сервиса.</p>\n<h2>Почему одинаковый payload обманывает</h2>\n<p>JSON-подобная форма скрывает решения. Число может означать деньги в минимальных единицах, счётчик или результат преобразования. Пустое поле может означать отсутствие значения, ошибку или значение по умолчанию. Время может быть timestamp, длительностью или логическим порядком событий. Если контракт не называет эти свойства, каждый адаптер заполняет пробел своим правилом.</p>\n<p>Нужен узкий boundary contract. Он не пытается описать всю систему и не переносит внутренние классы, stack trace, сборщик мусора или планировщик. Он отвечает на один вопрос: сохраняют ли три представления одну заранее названную форму. Поэтому в нём нет неявного default. Отсутствующее поле ведёт к отказу, а не к удобной подстановке.</p>\n<h2>Из чего состоит граница</h2>\n<p>В примере операция называется <code>fixed-order-decision</code>. Поле <code>value.tag</code> отделяет вид значения от его представления. <code>amountMinor: 4200</code> — учебное целое число; оно не объявляет денежный протокол и не должно автоматически превращаться во float. Поле <code>error</code> использует именованный конверт: в нём есть семантика, код и правило повтора. Это не объект исключения и не текст сообщения.</p>\n<p>Время задаётся двумя упорядоченными логическими отметками. Числа <code>100</code> и <code>108</code> дают разность восемь внутри учебной шкалы. Они не являются timestamp и не показывают latency. Каждый адаптер получает ту же версию схемы, тот же tag, ту же семантику ошибки, ту же шкалу времени и результат <code>exact</code>. Приведение типа скрывает потерю смысла, поэтому его надо отклонять.</p>\n<div class=\"table-scroll\"><table><caption>Минимальные поля общего контракта</caption><thead><tr><th scope=\"col\">Поле</th><th scope=\"col\">Зачем оно нужно</th><th scope=\"col\">Когда остановиться</th></tr></thead><tbody><tr><td><code>schemaVersion</code></td><td>Связывает верхний объект и адаптеры.</td><td>Версия пустая или различается.</td></tr><tr><td><code>value.tag</code></td><td>Называет вид значения до преобразования.</td><td>Tag отсутствует или подменён.</td></tr><tr><td><code>error</code></td><td>Фиксирует code и retry без object identity.</td><td>Нет именованного конверта.</td></tr><tr><td><code>time</code></td><td>Задаёт одну сравнимую шкалу.</td><td>Нет двух упорядоченных отметок.</td></tr><tr><td><code>mapping</code></td><td>Показывает сохранение формы.</td><td>Используется coercion вместо exact.</td></tr></tbody></table></div>\n<figure><img src=\"/assets/editorial/2026/multi-runtime-2026-contract-boundary-map.svg\" alt=\"Три модели PHP, JavaScript и D сходятся к одному именованному контракту и затем проходят ограниченную проверку\" loading=\"lazy\" /><figcaption>Схема показывает структуру границы. Она не изображает соединение процессов и не является трассировкой реальной системы.</figcaption></figure>\n<h2>Учебный пример в памяти</h2>\n<p>Ниже выполняется только JavaScript-код, который читает заранее заданный объект. Строки <code>php</code>, <code>javascript</code> и <code>d</code> — метки взглядов на форму, а не запущенные процессы. Пример полезен для проверки правил и отрицательных веток. Он не доказывает совместимость библиотек, транспортов или окружений.</p>\n<pre><code>const record = {\n id: 'named-contract-v1',\n schemaVersion: 'fixed-boundary-1',\n contract: {\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 adapters: [\n { model: 'php', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'named-envelope', timeBasis: 'fixed-logical-ticks', mapping: 'exact' },\n { model: 'javascript', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'named-envelope', timeBasis: 'fixed-logical-ticks', mapping: 'exact' },\n { model: 'd', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'named-envelope', timeBasis: 'fixed-logical-ticks', mapping: 'exact' }\n ]\n};\n\nconst models = new Set(record.adapters.map(({ model }) =&gt; model));\nconst accepted =\n record.schemaVersion === 'fixed-boundary-1' &amp;&amp;\n record.contract.time.closed &gt;= record.contract.time.opened &amp;&amp;\n ['php', 'javascript', 'd'].every((model) =&gt; models.has(model)) &amp;&amp;\n record.adapters.every((adapter) =&gt;\n adapter.contractVersion === record.schemaVersion &amp;&amp;\n adapter.valueTag === record.contract.value.tag &amp;&amp;\n adapter.errorSemantics === record.contract.error.semantics &amp;&amp;\n adapter.timeBasis === record.contract.time.basis &amp;&amp;\n adapter.mapping === 'exact'\n );\n\nconsole.log({ accepted, externalEffect: 'not-checked' });</code></pre>\n<p>Положительный результат означает: поля учебного объекта соответствуют названным правилам. <code>externalEffect: not-checked</code> удерживает смысл результата рядом с кодом. Если его убрать, читатель легко примет <code>accepted: true</code> за доказательство, что три системы связаны.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<div class=\"table-scroll\"><table><caption>Диагностика несогласованной границы</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Пропущенный amount читается как ноль.</td><td>Нет отдельного tag для отсутствия.</td><td>Сверить value.tag и наличие поля.</td><td>Добавить именованный вариант или остановить проверку.</td></tr><tr><td>Один адаптер хранит thrown value.</td><td>Смешаны semantics ошибки.</td><td>Сравнить errorSemantics буквально.</td><td>Выровнять конверт или вернуть stop-incomparable-adapter.</td></tr><tr><td>Для одного ответа считают duration.</td><td>Нет общей шкалы и закрывающей отметки.</td><td>Проверить basis, opened и closed.</td><td>Задать ordered fixed ticks; не подставлять часы.</td></tr><tr><td>Адаптер возвращает похожее число.</td><td>Форма прошла coercion.</td><td>Проверить mapping на exact.</td><td>Убрать приведение или описать новое поле и версию.</td></tr><tr><td>Пример называют интеграционным тестом.</td><td>Метки моделей приняли за процессы.</td><td>Перечислить реально запущенные компоненты.</td><td>Сузить вывод до проверки объекта в памяти.</td></tr></tbody></table></div>\n<h2>Порядок проверки</h2>\n<ol><li>Назовите одну операцию. Не смешивайте в одном объекте заказ, платёж и доставку.</li><li>Зафиксируйте версию схемы и tag значения. Не используйте «любой JSON».</li><li>Опишите ошибку отдельным именованным конвертом: semantics, code и retry.</li><li>Выберите одну шкалу времени и запишите обе упорядоченные отметки.</li><li>Добавьте по одной записи для PHP, JavaScript и D. Сверьте поля буквально.</li><li>Проверьте exact mapping. Любая скрытая конверсия должна вернуть отказ.</li><li>Прогоните положительный и отрицательные варианты. Сохраните status и точную причину.</li><li>Сформулируйте результат в пределах наблюдения: «форма объекта согласована», а не «интеграция работает».</li></ol>\n<h2>Отрицательный путь важнее happy path</h2>\n<p>Пустая версия или отсутствующий адаптер должны вернуть <code>stop-incomplete-contract</code>. Не надо принимать частичный объект ради продолжения разбора. Если JavaScript записывает <code>thrown-value</code>, а два других адаптера используют <code>named-envelope</code>, результат — <code>stop-incomparable-adapter</code>. Это не утверждение о поведении языков. Это точное описание несовпадения полей.</p>\n<p>Если <code>closed</code> отсутствует или basis равен <code>wall-clock</code>, верните <code>stop-undetermined-time-boundary</code>. Нельзя восстановить длительность из незаписанного события и нельзя превратить учебные ticks в метрику. Если новые требования делают эти поля недостаточными, создайте новую версию контракта. Не прячьте смысл в поле <code>metadata</code>.</p>\n<h2>Ограничения</h2>\n<p>Модель не описывает nullable semantics, ABI, сериализацию, transport, версии пакетов, доступы, retry конкретного клиента или бизнес-значение заказа. Она не запускает PHP, D или отдельный JavaScript runtime, не читает сеть и диск, не использует часы, не собирает telemetry, trace или profile и не содержит пользовательских данных. Поэтому из неё нельзя вывести latency, SLA, безопасность, совместимость релиза или готовность deploy.</p>\n<p>В production такой контракт может стать частью отдельного теста совместимости, но это потребует реальных входов, владельцев, версий и наблюдаемого результата. Учебный объект не заменяет этот тест. Он только не даёт начать разговор с ложного утверждения.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Материал и его пример готовы, если независимый читатель может повторить проверку по одному объекту и получить одно из двух: accepted с перечисленными полями или точный stop reason. При accepted все три model label присутствуют, версия совпадает, tag и error semantics совпадают, время упорядочено, mapping равен exact, а итог прямо говорит <code>externalEffect: not-checked</code>. При отказе причина указывает на конкретное недостающее поле. Ни один результат не использует слова «интеграция подтверждена» без отдельного runtime-доказательства.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://github.com/php/php-src/tree/b437f2b32eb364c9496d24abcc734272e5c9c980\" target=\"_blank\" rel=\"noopener noreferrer\">PHP source: annotated tag php-8.4.0</a> — официальный исходный срез PHP; он не подтверждает межъязыковую совместимость в этом примере.</li><li><a href=\"https://262.ecma-international.org/16.0/\" target=\"_blank\" rel=\"noopener noreferrer\">ECMA-262, 16th edition: ECMAScript 2025 Language Specification</a> — официальная спецификация ECMAScript; объект статьи остаётся учебной записью в памяти.</li><li><a href=\"https://github.com/dlang/dmd/tree/c403accecf9048802122c17852715251509adf56\" target=\"_blank\" rel=\"noopener noreferrer\">D compiler: annotated tag v2.111.0</a> — официальный исходный срез компилятора D; он не является доказательством запуска D-кода.</li></ul>"
}