function escapeHtml(value) { return String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); } const p = (text) => '

' + text + '

'; const h2 = (text) => '

' + text + '

'; const code = (text) => '
' + escapeHtml(text) + '
'; const ol = (items) => '
    ' + items.map((item) => '
  1. ' + item + '
  2. ').join('') + '
'; const figure = (src, alt, caption) => '
' + escapeHtml(alt) + '
' + caption + '
'; const table = (caption, headers, rows) => '
' + headers.map((cell) => '').join('') + '' + rows.map((row) => '' + row.map((cell) => '').join('') + '').join('') + '
' + caption + '
' + cell + '
' + cell + '
'; function plainText(html) { return html.replace(/<[^>]+>/g, ' ').replace(/&(?:quot|amp|lt|gt|#039);/g, ' ').replace(/\s+/g, ' ').trim(); } function bodyText(html) { return plainText(html.replace(/

Проверяемые источники<\/h2>[\s\S]*?(?=

|$)/, '')); } const REFERENCES = Object.freeze({ functions: { title: 'D Language Specification: Functions and Function Safety', url: 'https://dlang.org/spec/function.html', version: 'D language specification, page generated 25 July 2026', }, memory: { title: 'D Language Specification: Memory Safety', url: 'https://dlang.org/spec/memory-safe-d.html', version: 'D language specification, page generated 23 July 2026', }, abi: { title: 'D Language Specification: Application Binary Interface', url: 'https://dlang.org/spec/abi.html', version: 'D language specification, page checked 31 July 2026', }, cInterface: { title: 'D Language Specification: Interfacing to C', url: 'https://dlang.org/spec/interfaceToC.html', version: 'D language specification, page checked 31 July 2026', }, }); function sources(entries) { return ''; } export function validateDWorkload(input) { const throughput = Number(input?.throughput ?? 0); const latencyBudgetMs = Number(input?.latencyBudgetMs ?? 0); const nativeBoundary = Boolean(input?.nativeBoundary); const deploymentTargets = Number(input?.deploymentTargets ?? 0); if (!Number.isFinite(throughput) || throughput <= 0) return { decision: 'reject', reason: 'нужна измеримая нагрузка' }; if (latencyBudgetMs <= 0) return { decision: 'reject', reason: 'нужен бюджет задержки' }; if (deploymentTargets > 2 && !nativeBoundary) return { decision: 'compare', reason: 'широкий target matrix требует сравнить toolchain' }; if (nativeBoundary && throughput > 10000) return { decision: 'consider-d', reason: 'нагрузка и нативная граница дают основание проверить D' }; return { decision: 'keep-current-tool', reason: 'ограничения не требуют смены языка' }; } export function checkSafeBoundary(input) { const capacity = Number(input?.capacity ?? 0); const declaredLength = Number(input?.declaredLength ?? 0); const pointerChecked = Boolean(input?.pointerChecked); if (!pointerChecked) return { class: 'system', action: 'проверить указатель и владельца буфера вручную' }; if (!Number.isInteger(declaredLength) || declaredLength < 0 || declaredLength > capacity) return { class: 'reject', action: 'остановить вызов: длина выходит за буфер' }; return { class: 'safe-interface', action: 'передать slice с проверенной длиной' }; } export function validateCAbiPacket(packet, contract) { const errors = []; if (packet?.byteLength !== contract?.byteLength) errors.push('размер структуры'); if (packet?.endianness !== contract?.endianness) errors.push('порядок байтов'); for (const field of contract?.fields ?? []) { if (!Object.hasOwn(packet ?? {}, field.name)) errors.push('поле ' + field.name); } return { accepted: errors.length === 0, errors }; } function revision(meta, parts, referenceEntries) { const contentHtml = parts.join('') + h2('Проверяемые источники') + sources(referenceEntries); const proseLength = bodyText(contentHtml).length; if (proseLength < 5000 || proseLength > 15000) throw new Error(meta.slug + ': body length ' + proseLength); return Object.freeze({ ...meta, contentHtml, proseLength }); } const practice = revision({ slug: 'editorial-2027-03-practice-d-lessons', title: 'D для прикладной утилиты: сначала контракт входа, потом язык', categories: ['D', 'Инженерные практики'], cover: '/assets/editorial/2027/d-lessons-2027-runtime-tradeoff-map.svg', excerpt: 'Как понять, есть ли у задачи основание для D: нагрузка, native boundary, target matrix и стоимость toolchain.', readingMinutes: 15, }, [ p('Проблема выбора языка для утилиты начинается с красивого свойства: D компилируется в native binary, поддерживает контрактные проверки и даёт доступ к C ABI. Если принять это свойство за готовое решение, небольшая команда получает новый компилятор, пакетный менеджер и набор правил сборки, хотя исходная задержка могла возникать в запросе, формате файла или неверном контракте входа. Цена — месяцы сопровождения ради проблемы, которую язык не решает.'), p('Чтобы решение было инженерным, нужно сначала описать workload: сколько элементов проходит через программу, какой бюджет задержки, какие target-платформы, есть ли C-библиотека и как будет собираться бинарник. D появляется в таблице как один вариант рядом с текущим языком. Этот порядок не принижает язык. Он защищает проект от решения по вкусу и оставляет проверяемый критерий, когда выбор оправдан.'), h2('Что именно хотим улучшить'), p('«Нужна производительность» — слишком широкая формулировка. Для CLI важны время запуска, скорость обработки, память и размер артефакта. Для сервиса добавляется модель конкуренции, timeout и наблюдаемость. Для инструмента около C-библиотеки важны соглашение вызова, layout структуры и способ владения памятью. Один и тот же язык может быть уместен для одного пункта и лишним для другого.'), p('Опишите минимум два кандидата. Текущий инструмент часто выигрывает скоростью разработки и готовыми библиотеками; D может выиграть у места, где важны native deployment, явная работа с памятью или C ABI. Но каждое преимущество имеет стоимость: новый toolchain, обучение, platform packages, время сборки и диагностика production binary. Сравнение должно показывать эту цену рядом с эффектом.'), figure('/assets/editorial/2027/d-lessons-2027-runtime-tradeoff-map.svg', 'Карта выбора языка для прикладной утилиты: workload и ограничения ведут к сравнению D с текущим инструментом и проверяемому решению.', 'Схема начинает решение с нагрузки и ограничений. Ветка D появляется только после определения границы задачи, а не из-за отдельного свойства языка.'), table('Матрица выбора D для утилиты', ['Ограничение', 'Вопрос', 'Сигнал в пользу D', 'Цена решения'], [ ['Нагрузка', 'где расходуется CPU и память?', 'узкое место внутри вычисления', 'нужен профиль, а не предположение'], ['Native boundary', 'есть ли C ABI или системный вызов?', 'контракт можно проверить на границе', 'ручная проверка unsafe-участка'], ['Targets', 'сколько платформ и архитектур?', 'матрица поддерживается toolchain', 'сборки и бинарные артефакты'], ['Команда', 'кто будет читать и менять код?', 'есть owner и code review', 'обучение и время поддержки'], ['Доставка', 'как версионируется бинарник?', 'простая доставка без runtime', 'размер, лицензии, упаковка'], ]), h2('Учебный локальный фильтр требований'), p('Функция ниже делает не рекламный вывод, а фиксирует форму входа. В ней есть положительная ветка только тогда, когда одновременно названы нагрузка, latency budget и native boundary. Если target matrix шире двух платформ, результат предлагает сравнение, а не автоматический переход. Все числа учебные: перед решением их заменяют измерениями конкретной команды.'), code(`import { validateDWorkload } from './upgrade-2027-03.mjs'; const inputs = [ { throughput: 12000, latencyBudgetMs: 20, nativeBoundary: true, deploymentTargets: 1 }, { throughput: 300, latencyBudgetMs: 500, nativeBoundary: false, deploymentTargets: 1 }, { throughput: 12000, latencyBudgetMs: 20, nativeBoundary: false, deploymentTargets: 4 }, ]; for (const input of inputs) console.log(validateDWorkload(input)); // consider-d; keep-current-tool; compare`), p('Первый вход имеет узкую вычислительную задачу и нативную границу — D стоит проверить измерением. Второй не требует смены toolchain по заданным ограничениям. Третий слишком широк для одного выбора: сначала нужно сравнить способы сборки и доставки на всех targets. Функция полезна как шаблон карточки требований, а не как замена профилированию.'), h2('Контрактная проверка в D'), p('У D есть function contracts: precondition через in и postcondition через out. Это не универсальная валидация входа и не замена тестам. Контракт полезен, когда условие принадлежит самой функции: размер диапазона, допустимый индекс, инвариант результата. Для пользовательского файла всё равно нужна отдельная ошибка с безопасным сообщением и понятным форматом.'), p('При выборе языка не обещайте, что contract автоматически ускорит программу или найдёт бизнес-ошибку. Он проверяет условие в точке выполнения и зависит от режима сборки. Важнее сначала назвать, кто владеет условием: parser, domain service или boundary с C. Тогда один и тот же контракт можно повторить в тесте и в обработчике ошибки, не пряча смысл в assertion.'), h2('Действия по порядку'), ol([ 'Записать единицу нагрузки, бюджет задержки, размер данных, target-платформы и входные ограничения.', 'Найти измеряемый bottleneck и проверить, находится ли он внутри кода, который язык действительно изменит.', 'Сравнить D и текущий вариант по сборке, библиотекам, отладке, размеру бинарника и навыкам поддержки.', 'Для нативной границы описать C ABI, владение памятью и ошибку; unsafe-участок выделить отдельно.', 'Собрать минимальный прототип с одним workload и одинаковой методикой замера, затем зафиксировать результат и цену поддержки.', ]), h2('Ограничения и следующий шаг'), p('Фильтр требований не измеряет скорость и не говорит, что D лучше. Порог throughput в примере вымышленный и не переносится на другое железо. Контракты не устраняют логические ошибки, зависимость от внешней библиотеки или стоимость сборки. D также не делает код переносимым автоматически: platform ABI, linker и runtime остаются частью решения.'), p('Следующий шаг — взять одну горячую функцию и сделать парный прототип на текущем языке и D с одинаковым входом и выходом. Замерьте cold start, steady-state, память и время разработчика на исправление намеренной ошибки. Решение «остаться» будет таким же полезным результатом, как переход, если оно опирается на эти поля.'), ], [ { key: 'functions', use: 'Function contracts и атрибуты D используются для объяснения pre/post conditions и границы функции.', boundary: 'Спецификация не выбирает язык для продукта и не даёт benchmark конкретного workload.' }, { key: 'memory', use: 'Категории @safe, @trusted и @system используются при оценке нативной границы.', boundary: 'Memory safety не гарантирует отсутствие логических, portability и performance ошибок.' }, { key: 'abi', use: 'ABI-граница включена в матрицу выбора как часть доставки бинарника.', boundary: 'Спецификация ABI не описывает настройки конкретного компилятора и платформы.' }, ]); const mechanism = revision({ slug: 'editorial-2027-03-mechanism-d-lessons', title: 'D: @safe и @trusted на границе C API', categories: ['D', 'Безопасность памяти'], cover: '/assets/editorial/2027/d-lessons-2027-constraint-matrix.svg', excerpt: 'Как провести маленький unsafe-участок через проверенный интерфейс и не считать атрибут @safe доказательством всей системы.', readingMinutes: 16, }, [ p('Проблема FFI-кода появляется там, где D вызывает C-функцию с указателем и отдельной длиной буфера. Если длина пришла из другого источника, вызов может прочитать за пределами памяти, даже когда внешний метод выглядит коротким. Цена ошибки — повреждение памяти, падение процесса или уязвимость, которую трудно воспроизвести по обычному input. Один атрибут на публичной функции не исправляет неверное условие границы.'), p('В D для этой границы различаются @safe, @trusted и @system. @safe-код ограничивает операции, которые могут привести к memory corruption. @trusted разрешает узкий участок, но ответственность за его интерфейс остаётся у автора. @system не даёт компилятору такого обещания. Механизм работает, если unsafe-код короткий, его входы проверены, а наружу выходит безопасное представление данных.'), h2('Сначала проверяем размер, потом вызываем C'), p('C-функция часто получает pointer + length. Сам указатель не содержит длину, поэтому компилятор не может вывести, что заявленный диапазон действителен. В D безопасный wrapper должен принять массив или slice, проверить нужное условие и передать только диапазон, размер которого известен. Если C API требует null-terminated string, одного массива байт тоже недостаточно: нужна отдельная проверка завершающего байта.'), p('Изолируйте правила владельца. Если C-функция сохраняет указатель после возврата, wrapper должен либо запретить такой вызов, либо передать копию с понятным временем жизни. Атрибут scope помогает выражать ограничения escape там, где включена соответствующая проверка, но он не заменяет договорённость с внешней библиотекой. Любая функция, которая сохраняет адрес, требует отдельного чтения API и теста.'), figure('/assets/editorial/2027/d-lessons-2027-constraint-matrix.svg', 'Матрица границы D и C API: размер буфера, владелец, атрибут безопасности и допустимый результат проверки.', 'Схема связывает техническое ограничение с проверкой и стоп-условием. Зелёный путь начинается только после проверки длины и времени жизни.'), table('Роли атрибутов на FFI-границе', ['Уровень', 'Что разрешает', 'Что обязан проверить инженер', 'Типичная ошибка'], [ ['@safe', 'ограниченный набор операций', 'что вызовы и значения остаются безопасными', 'считать весь вызванный C безопасным'], ['@trusted', 'узкая ручная обёртка', 'инвариант указателя, длины и lifetime', 'поместить большой модуль в trusted'], ['@system', 'низкоуровневые операции', 'каждый callsite и контракт ABI', 'передать raw pointer без проверки'], ['slice', 'указатель и длина вместе', 'что slice не выходит за объект', 'довериться внешней length'], ]), h2('Локальная проверка буфера'), p('Вместо вызова реальной C-библиотеки сначала можно прогнать boundary checker на данных теста. Он принимает capacity, заявленную длину и признак проверки указателя. Результат разделяет отсутствие проверки, неверный диапазон и безопасный интерфейс. Это предметный пример входа в FFI: он проверяет именно опасную пару pointer/length, а не абстрактный статус карточки.'), code(`import { checkSafeBoundary } from './upgrade-2027-03.mjs'; const calls = [ { capacity: 16, declaredLength: 8, pointerChecked: true }, { capacity: 16, declaredLength: 24, pointerChecked: true }, { capacity: 16, declaredLength: 8, pointerChecked: false }, ]; for (const call of calls) console.log(checkSafeBoundary(call)); // safe-interface; reject; system`), p('Первый вход даёт диапазон внутри буфера. Второй останавливается до вызова: внешний контракт обещает 24 байта, а доступно 16. Третий не принимает решение за инженера, потому что адрес не прошёл проверку владельца. В D такой проверкой должен владеть маленький wrapper, а в тесте нужны граничные значения 0, capacity и capacity+1.'), h2('Что означает @trusted'), p('@trusted — не «проверено компилятором». Это обещание, что внешняя форма функции безопасна, хотя тело содержит операции, которые компилятор не может проверить. Поэтому у trusted-функции должны быть короткий исходник, явные preconditions и тесты на invalid length, null, пустой slice и повторный вызов. Не прячьте в ней преобразование формата, ownership и обработку ошибок одновременно.'), p('Если внешняя C-функция возвращает указатель, проверка должна ответить на два вопроса: объект жив и его размер известен? При ответе «нет» безопасный интерфейс невозможен без копирования или дополнительного контракта. После вызова нельзя использовать старый slice, если C-функция освобождает память. Ошибка lifetime часто переживает тесты на успешном input, поэтому негативная матрица обязательна.'), h2('Действия по порядку'), ol([ 'Прочитать C-прототип и зафиксировать смысл каждого указателя, длины, возвращаемого адреса и кода ошибки.', 'Выделить минимальный wrapper; не переносить внутрь @trusted парсинг, бизнес-правила и сетевой код.', 'Проверять указатель, диапазон, нулевую длину, overflow и время жизни до перехода в C.', 'Поставить unit tests на валидные и граничные значения, затем прогнать sanitizers или инструменты платформы.', 'Оставить публичную функцию @safe только при доказанном безопасном интерфейсе; остальную зону явно маркировать @system.', ]), h2('Ограничения и следующий шаг'), p('Проверка capacity в JavaScript — учебная модель числовой границы, а не анализ D-памяти. Она не видит aliasing, alignment, calling convention, null termination или освобождение в C. Даже корректный @safe wrapper может передать семантически неверный enum или структуру. Нужен compile-time и runtime тест именно тем компилятором и ABI, с которыми собирается продукт.'), p('Следующий шаг — выбрать один extern(C) вызов и оформить для него таблицу: pointer, length, ownership, error, thread-safety. Напишите маленький wrapper, который принимает D slice, и отдельно проведите review trusted-тела. Если один из пунктов не имеет ответа, вызов нельзя считать готовым к безопасной границе.'), ], [ { key: 'memory', use: 'Определения @safe, @trusted, @system, scope и границы memory safety.', boundary: 'Спецификация не проверяет контракт внешней C-библиотеки и не гарантирует portability или отсутствие логических ошибок.' }, { key: 'functions', use: 'Правила function attributes и contract expressions используются для precondition и postcondition.', boundary: 'Документация не создаёт unit tests и не решает lifetime конкретного объекта.' }, { key: 'cInterface', use: 'Интерфейс D/C и отдельные соглашения вызова используются для описания wrapper boundary.', boundary: 'Страница не подтверждает прототип, ABI и ownership неизвестной библиотеки.' }, ]); const field = revision({ slug: 'editorial-2027-03-field-d-lessons', title: 'D и C ABI: разобрать пакет до вызова', categories: ['D', 'Интеграции'], cover: '/assets/editorial/2027/d-lessons-2027-evidence-handoff-loop.svg', excerpt: 'Полевой разбор FFI-ошибки: размер структуры, порядок байтов и обязательные поля проверяются до вызова C.', readingMinutes: 15, }, [ p('Проблема C ABI редко выглядит как ошибка на строке вызова. D-программа передаёт структуру, C читает её и возвращает код, но значение поля оказывается неверным или процесс падает только на одной архитектуре. Цена — непредсказуемый сбой на границе, где обычный unit test видит только один компилятор и одну раскладку памяти. Чем дольше ошибка живёт, тем труднее отличить формат данных от ошибки бизнес-логики.'), p('Полевой разбор начинается с пакета, который реально пересекает ABI: byte length, endianness, поля, alignment и calling convention. Дальше нужно сопоставить его с C header и настройками компилятора. Нельзя проверять только имя struct. Два типа с одинаковыми полями могут иметь разный padding, порядок байтов или размер указателя. Пакет — это физический контракт, а не только исходный текст.'), h2('Размер структуры — первый стоп'), p('ABI определяет, как типы и функции представлены для взаимодействия с машинным кодом. Для структуры важны не только поля, но и выравнивание. Добавленный int может изменить offsets следующего поля; на 32- и 64-битной платформе размер указателя различается. Если D и C собраны с разными ожиданиями, чтение смещается, а ошибка проявится как «неверное значение» далеко от причины.'), p('Порядок байтов — отдельная ось. Файл может быть little-endian, а внешний протокол — big-endian; автоматическое копирование структуры не является преобразованием формата. Для числового поля запишите wire representation и проверяйте её на известном значении вроде 0x01020304. Так видно, поменялись байты или перепутана длина пакета.'), figure('/assets/editorial/2027/d-lessons-2027-evidence-handoff-loop.svg', 'Цикл проверки D и C ABI: пакет, размер, поля и порядок байтов проходят сопоставление с контрактом до вызова функции.', 'Схема отделяет физическую проверку пакета от самого вызова. Красная ветка возвращает данные на границу, если размер или поле не совпали.'), table('Проверка ABI-пакета до FFI-вызова', ['Проверка', 'Пример входа', 'Ожидаемое действие', 'Если пропустить'], [ ['byte length', '24 байта', 'сверить sizeof на обеих сторонах', 'смещение полей'], ['endianness', 'little-endian', 'декодировать число явно', 'неверный id или размер'], ['field set', 'version, flags, payload', 'проверить обязательные поля', 'чтение мусора'], ['alignment', 'offset 8 вместо 4', 'сверить compiler layout', 'сбой на другой архитектуре'], ['error code', '0 или отрицательное значение', 'перевести в D-ошибку', 'успех при частичном чтении'], ]), h2('Учебный валидатор пакета'), p('Локальная функция ниже принимает пакет и описание ожидаемой структуры. Она проверяет три вещи, которые можно увидеть ещё до вызова: размер, порядок байтов и набор полей. Числа и названия в примере учебные, но сам порядок повторяет рабочую проверку. В реальном проекте contract строится из header и результатов компилятора, а не из догадки автора wrapper.'), code(`import { validateCAbiPacket } from './upgrade-2027-03.mjs'; const contract = { byteLength: 12, endianness: 'little', fields: [{ name: 'version' }, { name: 'flags' }, { name: 'payload' }], }; const packet = { byteLength: 12, endianness: 'little', version: 2, flags: 1, payload: 4096 }; const broken = { byteLength: 16, endianness: 'big', version: 2, flags: 1 }; console.log(validateCAbiPacket(packet, contract)); console.log(validateCAbiPacket(broken, contract)); // { accepted: true, errors: [] } // { accepted: false, errors: ['размер структуры', 'порядок байтов', 'поле payload'] }`), p('Валидатор не вызывает C и поэтому не доказывает, что ABI корректен. Он делает видимыми три несовпадения до опасной операции. Для полноценного теста добавьте golden bytes, сборку маленького C helper и проверку результата на каждой целевой архитектуре. Смысл локального примера в том, что ошибка в contract table должна быть заметна раньше падения процесса.'), h2('Где заканчивается автоматическая проверка'), p('Размер и поля можно сравнить автоматически, но ownership и смысл flags требуют чтения C API. Поле payload может быть указателем, длиной или offset внутри того же пакета. Значение 0 может означать пусто, success или null. Не называйте результат «валидным пакетом», пока не проверены эти семантические значения и код ошибки.'), p('Особенно опасен частичный успех. C-функция могла записать структуру, но вернуть ошибку; D-код видит заполненное поле и продолжает обработку. Обёртка должна сначала проверить код возврата, затем интерпретировать output по версии и только потом отдавать его доменному коду. Версия пакета должна быть частью ключа выбора декодера, а не обычным полем, которое можно проигнорировать.'), h2('Действия по порядку'), ol([ 'Сохранить точный C header, compiler flags, target architecture и calling convention рядом с исходником wrapper.', 'Составить layout table с размером, offset, alignment, типом и смыслом каждого поля.', 'Проверить golden bytes для little/big-endian и граничных значений длины до вызова внешней функции.', 'Разделить код возврата, output и ошибку; не считать частично заполненную структуру успешным результатом.', 'Собрать тест на каждой поддерживаемой архитектуре и сохранить hex-пакет, версию контракта и итог проверки.', ]), h2('Ограничения и следующий шаг'), p('JavaScript-валидатор не моделирует padding, pointer alignment, compiler lowering и реальные байты. Он проверяет форму contract table, поэтому не заменяет C helper, D compiler и тест на целевом ABI. Документ D описывает правила языка, но конкретный vendor header может добавлять свои packing directives, версии и ownership соглашения.'), p('Следующий шаг — выбрать один extern(C) вызов, получить маленький golden packet и сравнить layout D/C в автоматической проверке. В отчёте оставьте hex, размер, архитектуру и код возврата. Это позволит отличить изменение компилятора от изменения данных и быстро вернуть ошибку к физической границе.'), ], [ { key: 'abi', use: 'Правила D ABI используются для объяснения layout, alignment и представления типов.', boundary: 'Спецификация не знает vendor header, compiler flags и архитектуру конкретного проекта.' }, { key: 'cInterface', use: 'Правила extern(C) и взаимодействия с C используются для выбора contract table и calling convention.', boundary: 'Документация не гарантирует корректность неизвестного прототипа или ownership.' }, { key: 'memory', use: 'Граница memory safety используется для отделения безопасного анализа пакета от raw pointer операций.', boundary: 'Проверка памяти не подтверждает семантику полей и код возврата внешней библиотеки.' }, ]); export const revisions = Object.freeze([practice, mechanism, field]); export function verifyRevisionsAgainstFixture() { const checks = revisions.map((item) => { const body = bodyText(item.contentHtml); return body.length >= 5000 && body.length <= 15000 && //.test(item.contentHtml) && /
/.test(item.contentHtml) && /
/.test(item.contentHtml) && /
    /.test(item.contentHtml) && !/(synthetic-plan-hand-off|productionEffect|future-only|plan\/scenario|source cutoff|not-collected|not-attempted|future owner|развитие автора)/i.test(body); }); const workload = validateDWorkload({ throughput: 12000, latencyBudgetMs: 20, nativeBoundary: true, deploymentTargets: 1 }); const abi = validateCAbiPacket({ byteLength: 4, endianness: 'little', version: 1 }, { byteLength: 4, endianness: 'little', fields: [{ name: 'version' }] }); const fixtureOk = workload.decision === 'consider-d' && abi.accepted; return Object.freeze({ passed: checks.filter(Boolean).length + (fixtureOk ? 1 : 0), total: checks.length + 1, accepted: checks.every(Boolean) && fixtureOk, characters: Object.fromEntries(revisions.map((item) => [item.slug, bodyText(item.contentHtml).length])) }); } if (process.argv.includes('--verify-fixture')) { const result = verifyRevisionsAgainstFixture(); process.stdout.write(JSON.stringify(result, null, 2) + '\n'); if (!result.accepted) process.exitCode = 1; } if (process.argv.includes('--print-revisions')) process.stdout.write(JSON.stringify(revisions) + '\n');