This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
function escapeHtml(value) { return String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); }
|
||||
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="' + 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 cloneFixed(value) { return JSON.parse(JSON.stringify(value)); }
|
||||
function deepFreeze(value) { if (value && typeof value === 'object' && !Object.isFrozen(value)) { Object.values(value).forEach(deepFreeze); Object.freeze(value); } return value; }
|
||||
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 = deepFreeze({
|
||||
d112: { title: 'D Programming Language — Change Log 2.112.0', url: 'https://dlang.org/changelog/2.112.0.html', version: 'D 2.112.0, released 07 January 2026' },
|
||||
d111: { title: 'D Programming Language — Change Log 2.111.0', url: 'https://dlang.org/changelog/2.111.0.html', version: 'D 2.111.0, released 01 April 2025' },
|
||||
d110: { title: 'D Programming Language — Change Log 2.110.0', url: 'https://dlang.org/changelog/2.110.0.html', version: 'D 2.110.0, released 07 March 2025' },
|
||||
});
|
||||
function sources(entries) { return '<ul>' + entries.map(({ key, use, boundary }) => { const ref = REFERENCES[key]; return '<li><a href="' + ref.url + '" target="_blank" rel="noopener noreferrer">' + escapeHtml(ref.title) + '</a> — pinned release material: ' + escapeHtml(ref.version) + '. ' + escapeHtml(use) + ' Граница: ' + escapeHtml(boundary) + '</li>'; }).join('') + '</ul>'; }
|
||||
|
||||
const FIXED_RUNTIME_CASES = deepFreeze({
|
||||
'planned-runtime-hand-off-v1': { id: 'planned-runtime-hand-off-v1', planDate: '2027-03', sourceCutoff: '2026-07-31', workload: { id: 'named-synthetic-request-shape', statement: 'question-only-no-service' }, constraints: [{ id: 'named-latency-budget-question', role: 'planned-constraint' }, { id: 'named-ownership-boundary-question', role: 'planned-constraint' }], benchmark: { state: 'not-run', configuration: 'not-collected', result: 'not-collected' }, requestedConclusion: 'synthetic-plan-hand-off', boundary: 'Fixed in-memory planning literal. No runtime selection, compiler execution, benchmark, profile, service scenario, repository, file, environment, clock, network, secret, telemetry, production system or customer data is read, created or changed.' },
|
||||
'undated-scenario-v1': { id: 'undated-scenario-v1', planDate: '', sourceCutoff: '2026-07-31', workload: { id: 'named-synthetic-request-shape', statement: 'question-only-no-service' }, constraints: [{ id: 'named-latency-budget-question', role: 'planned-constraint' }], benchmark: { state: 'not-run', configuration: 'not-collected', result: 'not-collected' }, requestedConclusion: 'synthetic-plan-hand-off', boundary: 'Negative fixed literal only.' },
|
||||
'unnamed-workload-or-constraint-v1': { id: 'unnamed-workload-or-constraint-v1', planDate: '2027-03', sourceCutoff: '2026-07-31', workload: { id: '', statement: '' }, constraints: [{ id: '', role: 'planned-constraint' }], benchmark: { state: 'not-run', configuration: 'not-collected', result: 'not-collected' }, requestedConclusion: 'synthetic-plan-hand-off', boundary: 'Negative fixed literal only.' },
|
||||
'hidden-benchmark-configuration-v1': { id: 'hidden-benchmark-configuration-v1', planDate: '2027-03', sourceCutoff: '2026-07-31', workload: { id: 'named-synthetic-request-shape', statement: 'question-only-no-service' }, constraints: [{ id: 'named-latency-budget-question', role: 'planned-constraint' }], benchmark: { state: 'not-run', configuration: 'hidden', result: 'not-collected' }, requestedConclusion: 'synthetic-plan-hand-off', boundary: 'Negative fixed literal only.' },
|
||||
'disallowed-winner-v1': { id: 'disallowed-winner-v1', planDate: '2027-03', sourceCutoff: '2026-07-31', workload: { id: 'named-synthetic-request-shape', statement: 'question-only-no-service' }, constraints: [{ id: 'named-latency-budget-question', role: 'planned-constraint' }], benchmark: { state: 'not-run', configuration: 'not-collected', result: 'not-collected' }, requestedConclusion: 'd-runtime-wins', boundary: 'Negative fixed literal only.' },
|
||||
});
|
||||
|
||||
export function createFixedRuntimeCase(id = 'planned-runtime-hand-off-v1') { const value = FIXED_RUNTIME_CASES[id]; return value ? deepFreeze(cloneFixed(value)) : undefined; }
|
||||
function stop(status, reason, nextAction) { return deepFreeze({ status, reason, nextAction, productionEffect: 'not-attempted' }); }
|
||||
export function assessFixedRuntimePlan(input) {
|
||||
if (!Object.values(FIXED_RUNTIME_CASES).some((item) => JSON.stringify(item) === JSON.stringify(input))) return stop('stop-unknown-fixed-input', 'input-is-not-a-known-named-fixed-literal', 'select-a-named-fixed-case');
|
||||
if (input.planDate !== '2027-03' || input.sourceCutoff !== '2026-07-31') return stop('stop-undated-scenario-or-cutoff', 'march-2027-plan-and-july-2026-cutoff-are-required', 'name-2027-03-and-2026-07-31');
|
||||
if (!input.workload?.id || !input.workload?.statement || !Array.isArray(input.constraints) || input.constraints.length < 1 || input.constraints.some((item) => !item.id || item.role !== 'planned-constraint')) return stop('stop-unnamed-workload-or-constraint', 'workload-and-every-constraint-must-be-named-synthetic-questions', 'name-the-workload-and-constraints');
|
||||
if (input.benchmark?.state !== 'not-run' || input.benchmark?.configuration !== 'not-collected' || input.benchmark?.result !== 'not-collected') return stop('stop-hidden-benchmark-configuration', 'a-future-benchmark-must-remain-not-run-with-no-configuration-or-result', 'keep-benchmark-as-not-run-and-not-collected');
|
||||
if (input.requestedConclusion !== 'synthetic-plan-hand-off') return stop('stop-disallowed-winner-or-positive-result', 'runtime-winner-or-positive-result-is-forbidden', 'use-synthetic-plan-hand-off');
|
||||
return deepFreeze({ status: 'synthetic-plan-hand-off', caseId: input.id, workload: deepFreeze(cloneFixed(input.workload)), constraints: deepFreeze(cloneFixed(input.constraints)), benchmark: deepFreeze(cloneFixed(input.benchmark)), boundary: input.boundary, productionEffect: 'not-attempted', nextAction: 'give-the-fixed-runtime-question-contract-to-a-future-evidence-owner' });
|
||||
}
|
||||
export function runFixedRuntimeFixture() { const expected = [['planned-runtime-hand-off-v1', 'synthetic-plan-hand-off'], ['undated-scenario-v1', 'stop-undated-scenario-or-cutoff'], ['unnamed-workload-or-constraint-v1', 'stop-unnamed-workload-or-constraint'], ['hidden-benchmark-configuration-v1', 'stop-hidden-benchmark-configuration'], ['disallowed-winner-v1', 'stop-disallowed-winner-or-positive-result']]; const checks = expected.map(([id, status]) => ({ id, expected: status, actual: assessFixedRuntimePlan(createFixedRuntimeCase(id)).status })); return deepFreeze({ passed: checks.filter((item) => item.expected === item.actual).length, total: checks.length, accepted: checks.every((item) => item.expected === item.actual) && Object.isFrozen(createFixedRuntimeCase()) && Object.isFrozen(createFixedRuntimeCase().constraints), checks: deepFreeze(checks) }); }
|
||||
|
||||
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 deepFreeze({ ...meta, contentHtml, proseLength }); }
|
||||
const refs = [
|
||||
{ key: 'd112', use: 'Зафиксирована дата последнего учитываемого официального D release и только перечень его изменений.', boundary: 'Не подтверждает выбор runtime, запуск, benchmark, profile или сервисный сценарий.' },
|
||||
{ key: 'd111', use: 'Использован как pinned официальный материал о D 2.111.0 и его версии.', boundary: 'Не превращает языковую возможность в результат сравнения.' },
|
||||
{ key: 'd110', use: 'Использован как pinned официальный material о D 2.110.0 и его дате.', boundary: 'Не даёт данных о чужой нагрузке, конфигурации или production effect.' },
|
||||
];
|
||||
|
||||
const practice = revision({ slug: 'editorial-2027-03-practice-d-lessons', title: 'Уроки D для прикладного инженера: практический маршрут', categories: ['Инженерные практики', 'Языки программирования'], cover: '/assets/editorial/2027/d-lessons-2027-runtime-tradeoff-map.svg', excerpt: 'План на март 2027: как поставить вопрос о D среди ограничений, не объявляя его языком по умолчанию.', readingMinutes: 21 }, [
|
||||
p('План на 2027-03 начинается не с установки D, а с конкретной дорогой ошибки: прикладной инженер принимает язык за готовый ответ до того, как назвал границы задачи. Тогда небольшая задача получает новый toolchain, сложный маршрут сборки и спор о вкусе, хотя исходная проблема могла быть в контракте, владении или задержке между слоями. Цена ошибки — не абстрактная «сложность», а время на интеграцию, повторные изменения и невозможность объяснить, что именно сравнивали.'),
|
||||
p('Вторая дорогая ошибка — описать будущий benchmark как уже существующий profile или сервисный сценарий. На редакторскую дату 2026-07-31 март 2027 ещё не наступил: у этого номера нет повторного profile, benchmark, запуска D, выбранного runtime, выпущенной версии или результата сравнения. Поэтому перед вами только явно маркированный план/сценарий на 2027-03 с source cutoff 2026-07-31. Единственный положительный output — <code>synthetic-plan-hand-off</code> и <code>productionEffect: not-attempted</code>.'),
|
||||
h2('Не начинать с языка по умолчанию'),
|
||||
p('D полезно рассматривать как один из кандидатов, когда вопрос уже имеет форму. Не «нужен ли быстрый язык», а «какой именованный участок требует проверяемого свойства и не ломает границу владения». Язык способен выразить ограничения, но не отменяет формат обмена, политику релиза, ownership зависимостей и стоимость сопровождения. Если эти условия не названы, сравнение превращается в выбор любимого инструмента, а не в инженерное решение.'),
|
||||
p('Практический маршрут намеренно оставляет D не-default. Для существующей системы, где основной риск — несовместимый контракт или отсутствие владельца, смена языка может не дать никакого полезного сигнала. Для маленькой утилиты, где уже согласован другой runtime, цена нового пути может оказаться выше потенциальной выгоды. Для участка с жёсткими ограничениями вопрос может быть уместен, но это ещё не означает, что D должен победить. Уместность — приглашение к проверке, а не verdict.'),
|
||||
figure('/assets/editorial/2027/d-lessons-2027-runtime-tradeoff-map.svg', 'Карта планового сравнения runtime: от именованной synthetic нагрузки и ограничений к вариантам D и не-D; красные блоки запрещают default, скрытый benchmark и объявление победителя.', 'Карта помогает сформировать будущий вопрос о компромиссах. Она не показывает существующий сервис, не выбирает runtime и не содержит результата benchmark.'),
|
||||
table('Карта контекстного сравнения на 2027-03', ['Вопрос', 'Допустимый плановый ответ', 'Почему D не default', 'Что пока неизвестно'], [
|
||||
['Нагрузка', 'named synthetic request shape', 'форма вопроса не равна реальной системе', 'объём, данные и окружение'],
|
||||
['Ограничение', 'named latency или ownership question', 'одно ограничение не заменяет остальные', 'приоритет и допустимый компромисс'],
|
||||
['Runtime', 'D и альтернативы как варианты', 'вариант не равен выбранному стеку', 'совместимость и стоимость доставки'],
|
||||
['Benchmark', 'not-run / not-collected', 'без config нельзя сравнивать', 'методика, machine, flags, samples'],
|
||||
['Вывод', 'synthetic-plan-hand-off', 'план не назначает winner', 'нужен ли отдельный scope'],
|
||||
]),
|
||||
h2('Сначала назвать нагрузку, затем границу'),
|
||||
p('Слово «нагрузка» нельзя оставлять декоративным. В этом draft <code>named-synthetic-request-shape</code> — только имя вопроса, без URL, клиента, количества запросов, payload и реального сервиса. Такой уровень намеренно слабый: он позволяет проверить, что вопрос назван, но не позволяет имитировать данные. Если будущей команде потребуется фактическая нагрузка, она должна появиться в отдельном authorised scope с собственными правилами доступа и provenance, а не быть тайно дописана в мартовский план.'),
|
||||
p('Ограничение должно быть столь же конкретным по форме. «Нужна производительность» не различает latency, throughput, старт, память, хвост распределения, стоимость обслуживания или риск интеграции. В literal есть <code>named-latency-budget-question</code> и <code>named-ownership-boundary-question</code>: это labels для будущей развилки, а не бюджет и не замер. Если имя отсутствует, evaluator останавливает сценарий. Подставлять удобное default-ограничение опаснее, чем вернуть пустую карточку: оно незаметно меняет смысл выбора.'),
|
||||
h2('Механизм практического решения'),
|
||||
p('Контекстное сравнение состоит из трёх разных операций. Первая фиксирует, что требуется различить: например, путь, где ограничение выражается в исходном коде, и путь, где оно обеспечивается вокруг кода. Вторая перечисляет, какие trade-off могут сделать сравнение нечестным: сборка, ABI, отладка, доступность библиотек, deployment и ownership. Третья решает, существуют ли полномочия и данные для benchmark. Смешать операции — значит сделать «быстро» одновременно требованием, методом и результатом.'),
|
||||
p('Официальные changelog D помогают удержать версионную дисциплину. На cutoff последняя используемая здесь публикация — D 2.112.0 от 07 January 2026; в ней перечислены изменения compiler, runtime и library. Это источник слов о конкретном release, но не источник о пригодности к нашей неописанной нагрузке. Равным образом упоминание 2.111.0 или 2.110.0 не доказывает, что будущая сборка совместима, что runtime выбран или что изменение будет заметно в сервисе.'),
|
||||
h2('Runnable literal вместо поддельного запуска'),
|
||||
code("import { createFixedRuntimeCase, assessFixedRuntimePlan } from './upgrade-2027-03.mjs';\n\nconst plan = createFixedRuntimeCase('planned-runtime-hand-off-v1');\nconst result = assessFixedRuntimePlan(plan);\nconsole.log({ status: result.status, effect: result.productionEffect });\n// { status: 'synthetic-plan-hand-off', effect: 'not-attempted' }"),
|
||||
p('Этот example runnable и безопасен, потому что работает только с fixed in-memory literal. Factory делает JSON clone и затем deep freeze всех вложенных значений; evaluator принимает JSON-равный известный literal и fail-closed закрывает другой input. Код не запускает компилятор, не выбирает D runtime, не читает файлы, environment, clock, сеть, secrets, profile или telemetry. Он проверяет только дисциплину будущего вопроса и не является microbenchmark, performance test или сервисной репетицией.'),
|
||||
h2('Порядок действий для будущего owner'),
|
||||
ol(['Зафиксировать <code>planDate: 2027-03</code> и <code>sourceCutoff: 2026-07-31</code>; без этой пары остановить сценарий.', 'Назвать synthetic workload без описания несуществующего сервиса, данных или profile.', 'Назвать каждое constraint и отдельно записать, кто вправе изменить его приоритет.', 'Поставить D рядом с альтернативами как subject of comparison, не как default и не как решение.', 'Оставить benchmark строго <code>not-run</code>, configuration/result — <code>not-collected</code>; скрытая configuration запрещена.', 'Передать только question contract будущему evidence owner; не объявлять winner, rollout, runtime selection или production gain.']),
|
||||
h2('Где полезно остановиться до сравнения'),
|
||||
p('Есть несколько случаев, когда честный маршрут заканчивается раньше benchmark. Если неизвестно, кто владеет boundary между вызывающим кодом и библиотекой, сначала нужен contract; иначе любой runtime будет оцениваться на плавающей постановке. Если delivery path не может принять новый compiler или артефакт, это не «минус D в целом», а ограничение конкретного контура. Если предметом тревоги служит только общее ощущение медленности, полезнее сначала назвать операцию и ожидание. Сценарий не обязан доходить до измерения, чтобы принести пользу: ранний stop сохраняет время, которое иначе ушло бы на спор о неназванной цели.'),
|
||||
p('Не-default позиция означает и право оставить текущий runtime. Такой выбор не требует доказывать, что существующий стек абсолютный победитель. Достаточно увидеть, что future question пока не различает варианты или что стоимость введения нового пути превышает известную ценность. Особенно вредно превращать отказ от эксперимента в скрытую оценку языка. «Не открываем scope» и «D не подходит» — разные statements: первое описывает решение о работе, второе потребовало бы evidence, которого в этом draft нет.'),
|
||||
h2('Как не спутать сравнение с миграцией'),
|
||||
p('Сравнение и миграция имеют разный предмет. Сравнение спрашивает, какой вариант стоит исследовать при заданной форме задачи. Миграция уже предполагает выбранную точку назначения, инвентаризацию зависимостей, переходную архитектуру, rollback и владение результатом. Если в карточку сравнения незаметно попадают слова «перенести», «заменить» или «выпустить», она начинает обещать организационную работу. P109 не создаёт такой работы: он не содержит списка модулей, совместимости, плана релиза или бизнес-эффекта.'),
|
||||
p('Точно так же нельзя использовать маленький runnable literal как proof of language behaviour. JavaScript validator здесь выбран только потому, что он проверяет редакционный контракт пакета; он не моделирует D, ABI или memory management. Его значение в том, что любой reader может увидеть одинаковый stop для недатированного сценария и declared winner. Будущее D-пример, если оно потребуется, должно быть собственным artifact с pinned compiler, исходным кодом и объяснённой целью. До этого граница между meta-check и техническим test должна оставаться видимой.'),
|
||||
h2('Ограничения маршрута и следующий шаг'),
|
||||
p('Маршрут не покрывает выбор команды, бюджет, лицензионную проверку, security review, ABI-интеграцию, compatibility matrix или обслуживание пакетов. Он не говорит, что D быстрее, безопаснее или дешевле какой-либо альтернативы. Публичный release material не заменяет фактов о target platform и не позволяет вычислить стоимость миграции. Особенно важно не подменять отсутствие benchmark словом «интуитивно»: интуиция может быть поводом сформулировать гипотезу, но не положительным результатом.'),
|
||||
p('Следующий шаг — будущему владельцу решить, открывать ли отдельный scope с названной workload, прозрачной конфигурацией benchmark и заранее согласованными критериями falsification. Если полномочия или данные не появляются, <code>synthetic-plan-hand-off</code> остаётся корректным финалом этого draft. В марте 2027 он может стать входом нового исследования, но не задним числом доказательством. Практический урок D здесь простой: сначала сделать условия выбора видимыми, затем позволить языку проиграть, если условия ему не подходят.'),
|
||||
], refs);
|
||||
|
||||
const mechanism = revision({ slug: 'editorial-2027-03-mechanism-d-lessons', title: 'Уроки D для прикладного инженера: как принять инженерное решение', categories: ['Архитектура', 'Языки программирования'], cover: '/assets/editorial/2027/d-lessons-2027-constraint-matrix.svg', excerpt: 'План на март 2027: runtime trade-off как проверяемая гипотеза с ограничениями и falsifiability.', readingMinutes: 21 }, [
|
||||
p('Конкретная проблема mechanism-статьи — спутать свойство языка со свойством готовой системы. После этого из фразы о безопасности, управлении памятью или компиляции делают обещание о latency и надёжности целого маршрута. Цена ошибки — неверный scope: инженер чинит или сравнивает runtime, хотя решение определяется границей FFI, планом сборки, аллокациями в библиотеке либо операционной моделью.'),
|
||||
p('Вторая проблема — скрыть условия будущего benchmark и затем назвать его победителя заранее. По состоянию на 2026-07-31 нет benchmark, повторного profile, service scenario, runtime selection, release D после cutoff или результатов сравнения для этого выпуска. Это только план/сценарий на 2027-03. Мы не утверждаем, что D или иной runtime выиграл; единственный допустимый positive outcome — synthetic plan hand-off с <code>productionEffect: not-attempted</code>.'),
|
||||
h2('Runtime trade-off — это не таблица сильных сторон'),
|
||||
p('Runtime trade-off полезен, когда он связывает именованное ограничение с конкретным механизмом и условием опровержения. «Меньше аллокаций» без пути исполнения — рекламный ярлык. «Нужен предсказуемый хвост» без метода наблюдения — только желание. В будущем сравнении каждое утверждение должно иметь владельца: кто определил workload, кто согласовал baseline, кто описал machine и compiler flags, кто вправе считать результат достаточным. Пока этих элементов нет, модель должна говорить not-collected, а не заполнять пробелы правдоподобными цифрами.'),
|
||||
p('D приносит в такой разговор выразительные языковые механизмы и собственный runtime-контекст, но не снимает обязанность проверить границу. Например, декларация атрибутов или изменение в release может изменить пространство вариантов; она не доказывает поведение интегрированного приложения. Changelog 2.112.0 фиксирует официальный перечень изменений релиза от 07 January 2026, а не performance claim для будущего сценария. Именно это различие защищает обсуждение от подмены «доступно в версии» на «подходит системе». '),
|
||||
figure('/assets/editorial/2027/d-lessons-2027-constraint-matrix.svg', 'Матрица ограничений runtime: именованные вопросы о задержке, владении, поставке и наблюдаемости сопоставлены с необходимым будущим evidence; нижняя строка запрещает скрытую конфигурацию и победителя.', 'Матрица показывает falsifiability будущего сравнения. Она не содержит измерений, профиля, runtime decision или факта о production.'),
|
||||
table('Матрица falsifiability будущего runtime-вопроса', ['Ограничение', 'Проверяемый механизм', 'Минимальный future evidence', 'Что опровергнет гипотезу'], [
|
||||
['Latency question', 'граница работы и измеряемая операция', 'полная benchmark configuration', 'не тот путь или не тот budget'],
|
||||
['Ownership question', 'владение памятью и API boundary', 'явный contract и lifecycle', 'скрытая передача или aliasing'],
|
||||
['Delivery question', 'compiler, runtime, packaging', 'target/flags/dependency record', 'несовместимый путь поставки'],
|
||||
['Observability question', 'какие события допустимо видеть', 'согласованный provenance', 'неполный или недоступный input'],
|
||||
['Сейчас', 'fixed literal validator', 'not-run/not-collected', 'любая claim о winner или result'],
|
||||
]),
|
||||
h2('Ограничения задают форму причинности'),
|
||||
p('Условия нельзя собирать после вывода. Если latency budget назван только в заключении, получится круг: выбранный runtime объявляют подходящим, потому что именно его измеряли, а измеряли его, потому что он уже объявлен подходящим. Если ownership boundary не зафиксирован, один вариант может незаметно переложить работу в обвязку, а другой — оставить её внутри процесса. Оба тогда выглядят разными языковыми ответами на вопрос, который на самом деле не был сформулирован.'),
|
||||
p('Falsifiability требует разрешить неприятный результат заранее. Для D это означает: определить, какие наблюдения будущего authorized scope сделают вариант неуместным, и не прятать их за словом «зависит». Это может быть неприемлемая поставка, нужда в отсутствующей интеграции, неоправданный operational burden или несовпадение с ownership rule. В нашем draft эти варианты не измеряются и не оцениваются. Но они должны быть названы как классы стоп-сигнала, иначе у сравнения остаётся лишь путь к заранее выбранному ответу.'),
|
||||
h2('Configuration — часть evidence, а не приложение мелким шрифтом'),
|
||||
p('Benchmark configuration — не техническая мелочь. В неё в будущем войдут хотя бы версия compiler/runtime, target, flags, машина, ввод, число прогонов, прогрев, baseline, способ агрегации и правило исключения выбросов. Пока ни одно из этих значений не определено в данном scope, нельзя написать даже «условно измерено». Наша fixed case прямо содержит <code>state: not-run</code>, <code>configuration: not-collected</code> и <code>result: not-collected</code>; любое другое значение останавливается как hidden configuration.'),
|
||||
p('Такой fail-closed ответ намеренно строже, чем обычная черновая заметка. Он не пытается угадать, какой флаг или machine будет разумным в 2027-03, потому что угадывание сразу стало бы частично вымышленным benchmark. Кроме того, оптимальная конфигурация зависит от вопроса: тестировать build time и response path одинаковой таблицей бессмысленно. Механизм статьи не предлагает template для всех сравнений; он требует, чтобы future owner сначала отделил benchmark contract от результата и смог показать оба.'),
|
||||
h2('Литеральная проверка границы'),
|
||||
code("import { createFixedRuntimeCase, assessFixedRuntimePlan } from './upgrade-2027-03.mjs';\n\nconst hidden = createFixedRuntimeCase('hidden-benchmark-configuration-v1');\nconsole.log(assessFixedRuntimePlan(hidden));\n// { status: 'stop-hidden-benchmark-configuration', productionEffect: 'not-attempted', ... }"),
|
||||
p('Пример не компилирует D и не измеряет программу. Он only evaluates a named in-memory literal, полученный через JSON clone и recursively deep freeze. Изменённый object или неизвестный id также закрываются, поэтому caller не может пронести результат под видом plan. Это маленький, но важный механизм: validator не утверждает, что configuration хорошая; он утверждает только, что у будущего comparison нет права скрывать её или заменить результатом ещё не проведённого запуска.'),
|
||||
h2('Официальный release не равен compatibility promise'),
|
||||
p('Источники этого номера намеренно ограничены official pinned primary D release materials. Они датируют 2.110.0, 2.111.0 и 2.112.0 и дают контекст изменениям, которые были опубликованы не позднее cutoff. Они не заменяются блогами, benchmark-агрегаторами, чужими кейсами или документацией, которая успела измениться после cutoff. Такая узость не обедняет draft: она не позволяет тихо импортировать современную репутацию экосистемы туда, где требуется доказательство конкретной интеграции.'),
|
||||
p('Из release notes можно корректно извлечь только ограниченные факты о release. Например, 2.112.0 перечисляет изменения compiler и runtime; это повод включить version pin в будущую конфигурацию. Некорректный следующий шаг — превратить список в утверждение, что определённая service path станет лучше. Между ними лежат target, код, allocator, libraries, ABI, операционная среда и метод измерения. Даже если все эти элементы появятся позже, это будет evidence нового scope, а не свойство этого мартовского текста.'),
|
||||
h2('Порядок для проверяемого trade-off'),
|
||||
ol(['Назвать synthetic workload и не дописывать несуществующий endpoint, client или объём.', 'Выделить ограничения: latency, ownership, delivery и observability не сводить к одному слову «быстро».', 'Для каждого ограничения назвать механизм и заранее записать, что могло бы его опровергнуть.', 'Зафиксировать, что benchmark пока not-run, а configuration и result not-collected.', 'Проверить литерал fail-closed validator; скрытая config, unnamed constraint и winner должны вернуть stop.', 'Передать only the question contract будущему owner, который отдельно решит доступ, методику и выбор runtime.']),
|
||||
h2('Почему один результат не закрывает trade-off'),
|
||||
p('Даже будущий корректный замер не отменит matrix. Один result отвечает только на один заранее описанный вопрос при одной configuration; он не переносится автоматически на другой target, другой вход или другой lifecycle. Это важно для прикладного инженера: желание закончить спор одной цифрой понятно, но часто именно цифра скрывает смену scope. Если ownership или delivery остаются нерешёнными, быстрый путь может быть плохим вариантом. Если latency не критична, сложная настройка может не окупить даже честно найденную разницу.'),
|
||||
p('Поэтому outcome будущего исследования должен быть двусторонним. Он может дать основание продолжить изучение D, оставить текущий вариант, изменить постановку или признать, что разница несущественна. Ни один из этих результатов нельзя назначить в мартовском draft. Здесь допускается только проверка готовности вопроса к такой развилке. Это меньше похоже на сравнительную презентацию, зато даёт получателю возможность проверить предпосылки до того, как значение метрики получит незаслуженный авторитет.'),
|
||||
h2('Ограничения механизма и следующий шаг'),
|
||||
p('Механизм не покрывает proof of correctness, security posture, лицензии, стоимость обучения, зрелость команды, policy закупок или поддержку через годы. Он не делает вывод о GC, manual memory control, @safe, Better C или определённом compiler: для такого вывода нужен отдельно зафиксированный вопрос и источник, а не общее имя D. Он также не говорит, что любой benchmark полезен. Плохо поставленный benchmark может дать больше уверенности и меньше знания, чем честный отказ от запуска.'),
|
||||
p('Следующий шаг — создать новый, явно authorised evidence scope только после того, как владелец примет named workload и constraint matrix. В нём можно будет выбрать версии, описать конфигурацию и сформулировать критерий остановки; сейчас этого нет. Если процесс не начнётся, outcome не меняется: <code>synthetic-plan-hand-off</code> не является провалом, а фиксирует, что вопрос пока поставлен без выдуманного выигрыша. Самый полезный урок runtime trade-off — сделать условия поражения видимыми до попытки найти победителя.'),
|
||||
], refs);
|
||||
|
||||
const field = revision({ slug: 'editorial-2027-03-field-d-lessons', title: 'Уроки D для прикладного инженера: кейс с ограничениями и выводами', categories: ['Полевые заметки', 'Инженерные решения'], cover: '/assets/editorial/2027/d-lessons-2027-evidence-handoff-loop.svg', excerpt: 'План на март 2027: synthetic evidence hand-off вместо report, profile или результата сравнения.', readingMinutes: 21 }, [
|
||||
p('Самая дорогая ошибка field-формата — выдать hand-off будущего исследования за готовый кейс. Читатель видит знакомые слова «profile», «сервис», «runtime» и считает, что уже есть данные, владелец, измерение и результат. Цена ошибки — ложная операционная память: следующая команда ищет несуществующий артефакт, защищает несуществующий вывод и может построить реальное решение на авторитете аккуратно написанного текста.'),
|
||||
p('Здесь этого не будет. На 2026-07-31 март 2027 ещё не наступил, поэтому P109 — только план/сценарий на 2027-03, source cutoff 2026-07-31. Нет повторного profile, benchmark, сервисного сценария, выбора runtime, выпуска D после cutoff или результата сравнения. Field focus означает synthetic evidence hand-off: карточку вопроса, которую будущий owner может принять, вернуть или развить в другом scope. Единственный positive outcome — <code>synthetic-plan-hand-off</code> с <code>productionEffect: not-attempted</code>.'),
|
||||
h2('Hand-off передаёт вопрос, а не доказательство'),
|
||||
p('В обычной полевой заметке полезно передать следы: конфигурацию, наблюдение, ссылку на код, лог, замер, дату и известный эффект. В этой заметке ни одного такого артефакта нет и не должно появиться. Synthetic hand-off содержит только date/cutoff, named synthetic workload, named planned constraints, состояние evidence и следующую роль. Он не содержит пользователя, проекта, machine, пакета, URL, команды, значения метрики или последовательности действий реальной системы.'),
|
||||
p('Такой аскетизм не делает передачу бесполезной. Он делает provenance проверяемым. Будущий получатель видит, что <code>not-collected</code> означает отсутствие сбора внутри текущей модели, а не потерянный файл. Он может спросить, нужна ли вообще реальная проверка, кто имеет право открыть доступ к данным и какие claims допустимы. Автор draft не навязывает ответ через детали, которые нельзя проверить. Внутри сценария только один результат считается хорошим: вопрос назван достаточно честно, чтобы его можно было передать без production action.'),
|
||||
figure('/assets/editorial/2027/d-lessons-2027-evidence-handoff-loop.svg', 'Петля synthetic evidence hand-off: дата, named workload и constraints проверяются до benchmark; при скрытой конфигурации или winner возникает stop, иначе question contract передаётся будущему владельцу.', 'Диаграмма описывает проверку fixed in-memory literals. Это не report о сервисе, profile, benchmark или принятый выбор runtime.'),
|
||||
table('Контракт передачи synthetic evidence', ['Поле', 'Допустимое состояние сейчас', 'Что получает будущий owner', 'Чего поле не доказывает'], [
|
||||
['Время', '2027-03 и cutoff 2026-07-31', 'границу редакционного знания', 'что мартовская работа сделана'],
|
||||
['Workload', 'named synthetic question', 'предмет для уточнения', 'существование сервиса или input'],
|
||||
['Constraints', 'named planned constraints', 'список развилок', 'приоритет или согласование'],
|
||||
['Benchmark', 'not-run / not-collected', 'запрет выдумывать config/result', 'измерение или profile'],
|
||||
['Conclusion', 'synthetic-plan-hand-off', 'право решить следующий scope', 'winner, rollout или gain'],
|
||||
]),
|
||||
h2('Отсутствие evidence тоже нужно маркировать'),
|
||||
p('Нельзя заменить отсутствие evidence нейтральным тоном. Фразы «на практике оказалось», «в сервисе видно» и «после повторного profile» звучат как обычная связка, но здесь они создают ложный факт. Даже если будущий reader легко вообразит такой путь, он не становится частью P109. У draft нет hidden fixture, приватной конфигурации, замера «для себя» или негласного решения. Если в 2027 году появится реальный материал, он должен жить в новом artefact с новой датой, scope, source set и ответственностью за данные.'),
|
||||
p('То же касается слов о release. Официальный D 2.112.0 от 07 January 2026 находится до cutoff и может быть указан как pinned primary material; однако это не означает, что именно эта версия будет взята будущей командой. Любая более поздняя версия, даже если когда-нибудь станет известна, не принадлежит этому тексту. Полезная передача не пытается быть живой сводкой. Она хранит границу знаний так, чтобы позднее обновление не маскировалось под то, что якобы было известно заранее.'),
|
||||
h2('Fail-closed оставляет выбор получателю'),
|
||||
p('Если workload или constraint неназваны, получатель не знает, что сравнивать и какое несоответствие важно. Если benchmark configuration скрыта, получатель не может судить о методе, но может ошибочно довериться числу. Если conclusion объявляет winner, ему остаётся только спорить с результатом, которого нет. Поэтому эти случаи не получают default-значения и не продолжаются «с оговорками». Validator возвращает precise stop, сохраняя возможность переписать вопрос до того, как он станет ложной задачей в чужой очереди.'),
|
||||
p('Fail-closed не говорит, что реальное исследование невозможно. Он говорит ровно обратное: для реального исследования нужен новый владелец evidence, явное право на данные и отдельная методика. У получателя остаётся несколько законных ответов: отклонить вопрос как неважный, запросить уточнение constraints, открыть discovery scope или согласовать transparent benchmark. Ни один из них не должен быть неявно выбран текстом. Это отличие hand-off от скрытого roadmap: hand-off передаёт неопределённость вместе с её границей.'),
|
||||
h2('Runnable contract передачи'),
|
||||
code("import { createFixedRuntimeCase, assessFixedRuntimePlan } from './upgrade-2027-03.mjs';\n\nconst rejected = createFixedRuntimeCase('disallowed-winner-v1');\nconst refusal = assessFixedRuntimePlan(rejected);\nif (refusal.productionEffect !== 'not-attempted') throw new Error('unexpected-effect');\nconsole.log(refusal.status);\n// stop-disallowed-winner-or-positive-result"),
|
||||
p('Этот runnable example deliberately demonstrates a refusal, not a success claim. Все значения fixed and in-memory; JSON clone отделяет literal от caller, deep freeze закрепляет nested workload, constraints и benchmark. Evaluator не обращается к D compiler, runtime, shell, files, network, environment, telemetry или clock. Его output не может стать deploy decision: productionEffect остаётся not-attempted и на stop, и на hand-off. Такой маленький contract полезен, потому что его поведение можно повторить без поддельного сервиса.'),
|
||||
h2('Порядок synthetic evidence hand-off'),
|
||||
ol(['Проверить, что hand-off явно относится к плану 2027-03 и сохраняет cutoff 2026-07-31.', 'Передать только named synthetic workload; не добавлять service name, profile, data sample или наблюдение.', 'Передать named constraints как вопросы, не как уже согласованные SLA, ownership или delivery policy.', 'Оставить benchmark not-run и configuration/result not-collected; любой hidden value вернуть с stop.', 'Не называть D, другой runtime или конфигурацию победителем и не писать positive result.', 'Отдать future evidence owner boundary и nextAction; решение об authorised scope остаётся за ним.']),
|
||||
h2('Что будущий owner должен проверить отдельно'),
|
||||
p('Если owner возьмёт вопрос, ему придётся создать собственный contract: что именно является workload, какие inputs разрешены, какую информацию нельзя сохранять, какие версии и target проверяются, как устроен baseline и кто читает результат. Нужно отдельно решить, является ли выбранная операция representative, какие ошибки измерения приемлемы и какой исход остановит работу. Эти решения не переносятся из P109, потому что P109 их не принимал. В этом и смысл honest hand-off: не выдавать пустой шаблон за готовую методику.'),
|
||||
p('Возможен и более короткий ответ: owner может решить, что сравнение не нужно, а проблема решается контрактом, кэшированием, разделением владения или изменением текущего пути. Такой итог не делает D «плохим» и не требует защитить альтернативу. Он лишь показывает, что конкретный runtime-question не получил достаточного основания. Хорошая field-передача допускает этот исход заранее; плохая превращает отсутствие запуска в давление «раз уж начали, надо доказать пользу». '),
|
||||
h2('Какие слова нельзя передавать по инерции'),
|
||||
p('Получателю стоит отдельно вычёркивать слова, которые создают несуществующий факт: «повторили», «подтвердили», «профиль показал», «сервис выдержал», «runtime выбран», «релиз вышел» и «победитель». В обычной заметке они могут быть точными, если рядом есть material и scope. В этом hand-off они меняют тип документа. Проблема не в уверенном тоне, а в том, что поздний reader уже не отличит заготовку от свидетельства и начнёт передавать эту подмену дальше.'),
|
||||
p('Вместо этого допустимы более точные конструкции: «планируется назвать», «будущему owner требуется решить», «configuration не собрана», «результат не получен», «вариант не выбран». Они не звучат эффектно, зато оставляют следующее действие там, где оно действительно может быть принято. У future owner появляется свобода изменить весь вопрос, а не только подобрать параметр к заранее обещанному benchmark. Это особенно ценно в прикладной работе, где контекст меняется быстрее, чем редакционный план.'),
|
||||
h2('Ограничения hand-off и следующий шаг'),
|
||||
p('Этот план не создаёт ticket, queue, benchmark configuration, owner, approval или production change. Он не проверяет compatibility, безопасность, доступность, стоимость, vendor support, licenses или performance. Публикации D 2.110.0, 2.111.0 и 2.112.0 помогают точно назвать датированные release materials, но не делают их evidence для неназванной системы. Здесь нет даже claim, что D будет рассмотрен в 2027-03: есть лишь сценарий того, как не выдать ожидание за результат.'),
|
||||
p('Следующий шаг — хранить this draft as a synthetic hand-off until a future owner explicitly authorises different work. Тогда новый artefact может содержать реальные данные и проверяемый вывод, но должен отличаться датой и provenance. До этого момента safest conclusion уже записан: question contract можно передать, production effect не пытались получить. Полевой урок для прикладного инженера не про красивый кейс; он про способность оставить следующему человеку честно ограниченное знание вместо убедительной, но вымышленной истории.'),
|
||||
], refs);
|
||||
|
||||
export const revisions = deepFreeze([practice, mechanism, field]);
|
||||
export function verifyRevisionsAgainstFixture() { const fixture = runFixedRuntimeFixture(); const articleChecks = revisions.map((item) => { const text = bodyText(item.contentHtml); return text.length >= 5000 && text.length <= 15000 && /(цен[аы]|стоимост|издержк|потер)/i.test(text.slice(0, 1000)) && /<table>/.test(item.contentHtml) && /<figure>/.test(item.contentHtml) && /<pre><code>/.test(item.contentHtml) && /<ol>/.test(item.contentHtml) && /2027-03/.test(text) && /2026-07-31/.test(text); }); return deepFreeze({ passed: fixture.passed + articleChecks.filter(Boolean).length, total: fixture.total + articleChecks.length, accepted: fixture.accepted && articleChecks.every(Boolean), fixture, articleChecks, 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');
|
||||
Reference in New Issue
Block a user