Files
progcode/web/scripts/upgrade-2027-03.mjs
T
2026-07-31 22:26:56 +03:00

273 lines
39 KiB
JavaScript
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.
function escapeHtml(value) {
return String(value).replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#039;');
}
const p = (text) => '<p>' + text + '</p>';
const h2 = (text) => '<h2>' + text + '</h2>';
const code = (text) => '<pre><code>' + escapeHtml(text) + '</code></pre>';
const ol = (items) => '<ol>' + items.map((item) => '<li>' + item + '</li>').join('') + '</ol>';
const figure = (src, alt, caption) => '<figure><img src="' + src + '" alt="' + escapeHtml(alt) + '" loading="lazy" /><figcaption>' + caption + '</figcaption></figure>';
const table = (caption, headers, rows) => '<div class="table-scroll"><table><caption>' + caption + '</caption><thead><tr>' + headers.map((cell) => '<th scope="col">' + cell + '</th>').join('') + '</tr></thead><tbody>' + rows.map((row) => '<tr>' + row.map((cell) => '<td>' + cell + '</td>').join('') + '</tr>').join('') + '</tbody></table></div>';
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>Проверяемые источники<\/h2>[\s\S]*?(?=<h2>|$)/, ''));
}
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 '<ul>' + entries.map(({ key, use, boundary }) => {
const reference = REFERENCES[key];
return '<li><a href="' + reference.url + '" target="_blank" rel="noopener noreferrer">' + escapeHtml(reference.title) + '</a> — версия и дата: ' + escapeHtml(reference.version) + '. Применение: ' + escapeHtml(use) + ' Граница: ' + escapeHtml(boundary) + '</li>';
}).join('') + '</ul>';
}
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 через <code>in</code> и postcondition через <code>out</code>. Это не универсальная валидация входа и не замена тестам. Контракт полезен, когда условие принадлежит самой функции: размер диапазона, допустимый индекс, инвариант результата. Для пользовательского файла всё равно нужна отдельная ошибка с безопасным сообщением и понятным форматом.'),
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-функция часто получает <code>pointer + length</code>. Сам указатель не содержит длину, поэтому компилятор не может вывести, что заявленный диапазон действителен. В D безопасный wrapper должен принять массив или slice, проверить нужное условие и передать только диапазон, размер которого известен. Если C API требует null-terminated string, одного массива байт тоже недостаточно: нужна отдельная проверка завершающего байта.'),
p('Изолируйте правила владельца. Если C-функция сохраняет указатель после возврата, wrapper должен либо запретить такой вызов, либо передать копию с понятным временем жизни. Атрибут <code>scope</code> помогает выражать ограничения 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 определяет, как типы и функции представлены для взаимодействия с машинным кодом. Для структуры важны не только поля, но и выравнивание. Добавленный <code>int</code> может изменить offsets следующего поля; на 32- и 64-битной платформе размер указателя различается. Если D и C собраны с разными ожиданиями, чтение смещается, а ошибка проявится как «неверное значение» далеко от причины.'),
p('Порядок байтов — отдельная ось. Файл может быть little-endian, а внешний протокол — big-endian; автоматическое копирование структуры не является преобразованием формата. Для числового поля запишите wire representation и проверяйте её на известном значении вроде <code>0x01020304</code>. Так видно, поменялись байты или перепутана длина пакета.'),
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. Поле <code>payload</code> может быть указателем, длиной или offset внутри того же пакета. Значение <code>0</code> может означать пусто, 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 && /<table>/.test(item.contentHtml) && /<figure>/.test(item.contentHtml) && /<pre><code>/.test(item.contentHtml) && /<ol>/.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');