diff --git a/editorial/production/README.md b/editorial/production/README.md index 67d1f66..6fb6a10 100644 --- a/editorial/production/README.md +++ b/editorial/production/README.md @@ -1,6 +1,6 @@ # Производство редакционных партий -На 31 июля 2026 года строгий аудит проходит 331 из 358 созданных материалов. Остальные 27 не считаются «почти готовыми»: их нужно заменить, а не косметически удлинить. +На 31 июля 2026 года строгий аудит проходит 334 из 358 созданных материалов. Остальные 24 не считаются «почти готовыми»: их нужно заменить, а не косметически удлинить. ## Одна партия diff --git a/editorial/reviews/2027-04-draft.md b/editorial/reviews/2027-04-draft.md new file mode 100644 index 0000000..3f21481 --- /dev/null +++ b/editorial/reviews/2027-04-draft.md @@ -0,0 +1,96 @@ +# P110 — April 2027: «Эволюция frontend-сборки» + +## Статус, temporal boundary и write scope + +Редакторская дата: **2026-07-31**. Апрель 2027 ещё не наступил. Три статьи — явно помеченный **план/сценарий на 2027-04** с source cutoff **2026-07-31**. В них не существуют и не заявляются: реальный config, CI artifact, build timing, cache hit, bundle size, migration, tool release, build result, winner, rollout или production change. + +Очередь P110 запрашивает исторические config/CI-артефакты и одинаковый build-замер. В этом наборе они смоделированы только как именованные поля synthetic shape со значением `not-collected`; это не данные, не фрагменты системы и не скрытая методика. Единственный разрешённый положительный output — `synthetic-plan-hand-off`, всегда с `productionEffect: not-attempted`. + +Изменены только: + +- `web/scripts/upgrade-2027-04.mjs` +- `editorial/reviews/2027-04-draft.md` +- `web/public/assets/editorial/2027/build-evolution-2027-configuration-timeline.svg` +- `web/public/assets/editorial/2027/build-evolution-2027-comparable-conditions-matrix.svg` +- `web/public/assets/editorial/2027/build-evolution-2027-evidence-handoff-loop.svg` + +Registry, README, `articles.json`, app, queue, P102–P109 и Git state не менялись. + +## Pinned official primary sources + +1. [webpack — Caching guide, immutable source snapshot](https://github.com/webpack/webpack.js.org/blob/b7fa07c852d24449e9445717c41395bdd2ca4784/src/content/guides/caching.mdx), official webpack documentation at immutable `b7fa07c852d24449e9445717c41395bdd2ca4784`, resolved on source cutoff **2026-07-31**. Используется только для терминов output/content hash/cache как частей configuration; не доказывает local config, timing, artifact или размер. +2. [actions/cache v4.2.3 — immutable README](https://github.com/actions/cache/blob/5a3ec84eff668545956fd18022155c47e93e2684/README.md), official GitHub Action at immutable `5a3ec84eff668545956fd18022155c47e93e2684`, pinned before source cutoff **2026-07-31**. Используется только для различения cache key, restore и save; не подтверждает workflow, runner, cache hit или запуск. +3. [SLSA v1.0 — Provenance](https://slsa.dev/spec/v1.0/provenance), official SLSA specification v1.0, pinned before cutoff. Используется как vocabulary для provenance артефакта; не утверждает существование, создание или проверку attestation. + +Все источники первичные, official, фиксированы не позднее cutoff. Никаких источников о будущих релизах инструментов, будущем CI или будущих build results не использовано. + +## Review pass 1 — редакционный фокус, голос и temporal honesty + +- **Practice**: сравнение configuration до названия инструмента. В первых двух абзацах названы цена ложного «новая лучше» и цена выдуманной CI-картины. Timeline ведёт к question hand-off, а не к migration result. +- **Mechanism**: cache, CI, artifact и предел inference. В первых двух абзацах разделены разные слои evidence и цена сравнения разных условий. Matrix отделяет future provenance от недопустимых выводов о timing, hit и размере. +- **Field**: передача synthetic evidence, а не build report. В первых двух абзацах названы цена ложной операционной памяти и подмена пустой карточки полевым отчётом. Loop заканчивается future owner, не approval или rollout. +- Все тексты держат M10: прагматичный русский технический голос, без hype по инструментам и без retrospective tone. У каждого есть mechanism, доступная HTML table, meaningful русский alt/caption, runnable safe example, ordered actions, limitations и next step. + +## Review pass 2 — literal safety, отсутствие фактов и fail-closed + +- `configuration-hand-off-v1` требует `planDate: 2027-04`, `sourceCutoff: 2026-07-31`, две named synthetic configurations, named `comparableCondition` со state `not-collected`, а также `config`, `ci`, `buildTiming`, `cacheHit` и `bundleSize` строго `not-collected`. +- `undated-future-scenario-v1` возвращает `stop-undated-future-scenario-or-cutoff`. +- `missing-comparable-condition-v1` возвращает `stop-missing-comparable-condition`. +- `hidden-configuration-or-cache-assumption-v1` возвращает `stop-hidden-configuration-or-cache-assumption`; скрытая configuration и assumed cache не получают default. +- `disallowed-positive-result-v1` возвращает `stop-disallowed-positive-result`; нельзя заявить build improvement, migration или иной future outcome. +- Factory делает JSON clone fixed literal и recursive deep freeze, включая `historicalArtifacts`. Evaluator принимает только JSON-равный известный literal; unknown или mutation fail-closed. Модуль не читает файлы или конфигурацию, не запускает build/CI/cache и не обращается к environment, clock, network, secrets, telemetry или production. + +## Review pass 3 — accessibility, visual inspection и uniqueness + +- Все три SVG имеют русские `title` и `desc`, а статьи — осмысленные русские alt/caption. В SVG нет URL, измеренных значений или claims о build result. +- XML/safety проверка исключает `script`, `foreignObject`, `javascript:`, `data:image` и inline handlers. +- Sharp 375 px visual inspection проведена по PNG. Timeline: читаются четыре верхних шага, обе stop-ветви и подпись. Matrix: сохраняются пять строк, четыре колонки и нижний красный guard. Loop: различимы вход, дата, сравнимость, hand-off и центральный stop; текст не обрезан. +- Strict pairwise uniqueness включает code и исключает section `Проверяемые источники`. Для каждой пары `shared-12-word-fragments=0`. + +## Actual checks and results + +Выполнено из `web/`: + +```sh +node --check scripts/upgrade-2027-04.mjs +node scripts/upgrade-2027-04.mjs --verify-fixture +npm run audit:draft -- scripts/upgrade-2027-04.mjs +node --input-type=module -e "import { createFixedBuildCase, assessFixedBuildPlan } from './scripts/upgrade-2027-04.mjs'; const ids=['configuration-hand-off-v1','undated-future-scenario-v1','missing-comparable-condition-v1','hidden-configuration-or-cache-assumption-v1','disallowed-positive-result-v1']; for (const id of ids) console.log(id, assessFixedBuildPlan(createFixedBuildCase(id)).status); const accepted=assessFixedBuildPlan(createFixedBuildCase()); console.log('exports-frozen', Object.isFrozen(createFixedBuildCase()), Object.isFrozen(createFixedBuildCase().historicalArtifacts), accepted.productionEffect);" +xmllint --noout public/assets/editorial/2027/build-evolution-2027-configuration-timeline.svg public/assets/editorial/2027/build-evolution-2027-comparable-conditions-matrix.svg public/assets/editorial/2027/build-evolution-2027-evidence-handoff-loop.svg +! rg -n -i '<(script|foreignObject)\\b|javascript:|data:image|(?:^|[[:space:]])on[a-z]+=' public/assets/editorial/2027/build-evolution-2027-configuration-timeline.svg public/assets/editorial/2027/build-evolution-2027-comparable-conditions-matrix.svg public/assets/editorial/2027/build-evolution-2027-evidence-handoff-loop.svg +node --input-type=module -e "import sharp from 'sharp'; for (const n of ['build-evolution-2027-configuration-timeline','build-evolution-2027-comparable-conditions-matrix','build-evolution-2027-evidence-handoff-loop']) await sharp('public/assets/editorial/2027/'+n+'.svg').resize({width:375}).png().toFile('/private/tmp/'+n+'-375.png');" +node --input-type=module -e "import { revisions } from './scripts/upgrade-2027-04.mjs'; const text=h=>h.replace(/
' + text + '
'; +const h2 = (text) => '' + escapeHtml(text) + '';
+const ol = (items) => '| ' + cell + ' | ').join('') + '
|---|
| ' + cell + ' | ').join('') + '
synthetic-plan-hand-off с productionEffect: not-attempted. Остальное в этом тексте — вопрос, а не наблюдение.'),
+ h2('Сравнивать не ярлыки, а действующие условия'),
+ p('Название сборщика слишком грубо для инженерного решения. Оно не показывает, какие entry points существуют, какие transforms включены, куда попадает CSS, какие внешние зависимости считаются частью output и какую цель преследует сборка. Два процесса с одинаковой надписью могут различаться сильнее, чем два процесса с разными названиями. Поэтому практический старт — назвать левую и правую configuration как synthetic placeholders, а не объявить один инструмент «устаревшим».'),
+ p('В этом материале named-synthetic-baseline-configuration и named-synthetic-candidate-configuration не являются файлами, package manifest или fragment реального pipeline. Это фиксированные in-memory literals, запрещающие подмену вопроса правдоподобными деталями. Они помогают сформулировать будущему owner задачу: восстановить provenance каждого условия, если для этого появится отдельный scope. Пока доказательств нет, даже слово «baseline» здесь не означает существующую сборку.'),
+ figure('/assets/editorial/2027/build-evolution-2027-configuration-timeline.svg', 'Русская временная схема: от именования двух synthetic конфигураций через условия сравнения к future evidence hand-off; красные остановки запрещают недатированный сценарий и вывод о преимуществе.', 'Схема описывает плановый порядок вопросов на 2027-04. Она не содержит конфигурации проекта, CI-лога, артефакта или измерения.'),
+ table('Что требуется сопоставить до разговора о замене', ['Плоскость', 'Допустимое имя в плане', 'Чего это не доказывает', 'Будущий вопрос'], [
+ ['Вход', 'named synthetic application shape', 'что приложения одинаковы', 'какой route и input сравниваем'],
+ ['Configuration', 'baseline / candidate placeholder', 'что известны flags и plugins', 'какие параметры имели provenance'],
+ ['Output', 'artifact question', 'размер или состав файла', 'какой manifest и способ подсчёта нужен'],
+ ['CI', 'comparable condition question', 'runner, очередь или timing', 'какая среда считается одинаковой'],
+ ['Итог', 'synthetic-plan-hand-off', 'улучшение или завершение миграции', 'нужен ли authorised evidence scope'],
+ ]),
+ h2('Configuration — это контракт, а не фон'),
+ p('Практическая карта должна отделять намерение от механизма. Намерение можно назвать без факта: например, будущая сторона сравнения должна отвечать на один и тот же вопрос о доставляемом клиентском output. Механизм требует списка условий: source revision, dependency resolution, target environment, transform chain, optimisation policy, asset treatment и правила очистки output. Пока ни одно из этих значений не собрано, их нельзя заменить общими словами «обычная production-сборка».'),
+ p('Это ограничение защищает и от симметричной ошибки. Не следует защищать прошлый путь только потому, что у него привычный config. Привычность не свидетельствует о неизменности входов, качества кэша или пригодности артефакта. Сравнение начинается с симметричного пробела знаний: обе стороны получают явные placeholders, а доказательства для обеих должны иметь одинаково читаемую provenance. Иначе новая сторона обязана раскрывать условия, а старая остаётся легендой.'),
+ h2('Почему cache нельзя считать результатом'),
+ p('Кэш может участвовать в будущем условии, но не должен становиться украшением текста. Cache key, restore source, момент сохранения, доступность workspace и холодное состояние способны менять ход job; из этого не следует ни конкретная семантика CI, ни будущий cache hit. Даже если инструментальная документация описывает caching, она описывает возможности и ограничения механизма, а не событие в чужом workflow. В P110 поле cacheHit оставлено not-collected.'),
+ p('Тот же принцип работает для artifact. Bundle size — это не свойство ярлыка и не обязательный признак прогресса. Нужно заранее согласовать, что считается output, как обходятся source map, сжатие, generated assets, code splitting и служебные файлы. Нельзя называть число, пока не определены единица, состав и момент сбора. Меньшее значение без договора способно скрыть вынесенный ресурс, изменённый путь или просто другую границу подсчёта.'),
+ h2('Runnable пример проверяет границу утверждения'),
+ code("import { createFixedBuildCase, assessFixedBuildPlan } from './upgrade-2027-04.mjs';\n\nconst question = createFixedBuildCase('configuration-hand-off-v1');\nconst handoff = assessFixedBuildPlan(question);\nconst planOnly = handoff.status === 'synthetic-plan-hand-off'\n && handoff.productionEffect === 'not-attempted';\nconsole.log(planOnly ? 'question-contract' : 'reject');\n// question-contract"),
+ p('Пример буквально запускается только над named fixed in-memory literal. Factory сначала делает JSON clone, затем рекурсивный deep freeze; caller не получает общую изменяемую ссылку. Evaluator принимает лишь JSON-равный известный literal и fail-closed отклоняет произвольный object. Код не читает конфигурацию, не вызывает CI, не создаёт артефакт, не запускает build и не смотрит часы, environment, сеть, secrets или telemetry. Поэтому его output — контроль честности hand-off, не synthetic benchmark.'),
+ h2('Как читать старую и новую сторону без ретроспективы'),
+ p('Когда будущая работа всё-таки получит доступ к материалам, полезно не превращать их в один огромный diff. Сначала читают договорённость о входе: откуда берётся исходный код, какие generated parts считаются входом, какая зависимость фиксируется и что не входит в вопрос. Потом отдельно смотрят преобразования и output contract. Такое разбиение не гарантирует, что сравнение возможно, зато быстро показывает, где одна из сторон описана лишь частично. До появления материалов это не инструкция на конкретные команды, а порядок будущего чтения.'),
+ p('Слово «новая» тоже нуждается в разборе. Оно может означать другой toolchain, но также другой layout проекта, новый способ разделять код, иной формат статических файлов или иной контракт с платформой доставки. Если эти изменения свернуть в один коммит и один вывод, невозможно определить, что именно следует откатывать при проблеме. Плановая карточка поэтому не требует доказать превосходство candidate. Она требует назвать, какие независимые изменения вообще могут существовать и какие из них нельзя скрывать за общим названием migration.'),
+ h2('Одинаковый вопрос не обязан иметь одинаковый ответ'),
+ p('Comparable condition не пытается стереть реальность двух подходов. Условия могут позволять разную структуру output, если именно эта разница входит в заранее названный вопрос. Недопустимо другое: одной стороне разрешают исключить ресурс из подсчёта, а другой нет; одной стороне дают подготовленное состояние, а для другой измеряют первое получение зависимостей. В таких случаях корректный conclusion не «кто-то проиграл», а «собранные записи не отвечают на один и тот же вопрос». Это ценный отрицательный результат будущего evidence scope.'),
+ p('Нужна также граница между сравнением для пользователя и сравнением для разработчика. Стабильность хеша, размер доставляемого набора, длительность локального rebuild и длительность CI job могут быть отдельными вопросами с разными input и ценой. Склеить их в слово «быстрее» — значит дать метрике чужого уровня решать архитектуру. P110 не выбирает, какой из вопросов важнее. Он требует, чтобы future owner не использовал сигнал одного уровня как доказательство результата на другом.'),
+ h2('Честная запись о том, чего нет'),
+ p('Иногда команда опасается, что поле not-collected выглядит как незавершённость. В плановом документе это наоборот полезная информация: она сообщает, что автор видел необходимость evidence, но не получил права или материала его собрать. Такое поле делает hand-off проверяемым: поздний читатель видит, что отсутствует именно config, CI artifact или cache observation, а не всё знание о задаче. Вымышленный ноль, примерный процент или безымянный «успех» были бы хуже, потому что создавали бы ложный объект для обсуждения.'),
+ p('Порог для перехода к данным следует назвать заранее: новый scope должен позволять читать конкретные inputs, сохранять их provenance и обсуждать последствия их публикации. Без этого даже аккуратная команда рискует вынести служебные детали в редакционную статью или, наоборот, заменить их обобщением, которое больше нельзя проверить. В текущем plan такого порога нет. Он заканчивается не началом работы с системой, а точной причиной, по которой следующему владельцу потребуется отдельное решение.'),
+ h2('Порядок действий для будущего владельца'),
+ ol(['Сохранить пару planDate: 2027-04 и sourceCutoff: 2026-07-31; недатированный future scenario остановить.', 'Назвать две стороны как placeholders и не подставлять реальный config без отдельного разрешения.', 'Определить один named comparable condition: input, границу output и среду должны можно было прочитать у обеих сторон.', 'Запросить provenance historical config и CI artifact; до получения оставить все поля not-collected.', 'Отдельно решить, нужен ли build-запуск; если нужен, документировать методику до запуска, а не после числа.', 'Передать question contract future evidence owner без winner, migration claim и production effect.']),
+ h2('Четыре стоп-сигнала в практической карточке'),
+ p('Практическая карточка должна быть способна назвать свою причину остановки. undated-future-scenario-v1 не даёт спрятать обещание за неопределённым «потом». missing-comparable-condition-v1 не позволяет выдать два похожих запуска за одну проверку. hidden-configuration-or-cache-assumption-v1 не заменяет неизвестный config или cache удобным default. Наконец, disallowed-positive-result-v1 закрывает «сборка улучшена», пока нет отдельного evidence record. Эти labels не описывают инцидент; они делают видимой форму ошибки в плановом документе.'),
+ h2('Ограничения и следующий шаг'),
+ p('Этот practice не говорит, какой bundler выбрать, как переписать config, какую версию выпустить или какие шаги CI использовать. Он не оценивает совместимость, безопасность, лицензии, доступность plugin, стоимость runner и риск миграции. Официальные источники внизу дают язык для разговора о caching и provenance, но не поставляют ни одного local artifact. Их нельзя использовать как замену историческому config или записи запуска.'),
+ p('Следующий шаг — не «начать миграцию», а решить, есть ли полномочия собрать доказательства для названного condition. Если такие полномочия появятся, новый материал обязан явно отделить реальные inputs, метод и результат от этого апрельского плана. Если нет, корректно оставить hand-off незакрытым. Это менее эффектно, чем обещание эволюции, но дешевле, чем сделать архитектурное решение на основании названия инструмента.'),
+], refs);
+
+const mechanism = revision({ slug: 'editorial-2027-04-mechanism-build-evolution', title: 'Эволюция frontend-сборки: сравнимость кэша, CI и артефакта', categories: ['Frontend', 'Сборка'], cover: '/assets/editorial/2027/build-evolution-2027-comparable-conditions-matrix.svg', excerpt: 'План на апрель 2027: механизм честного сравнения конфигураций без выдуманного времени сборки и cache hit.', readingMinutes: 23 }, [
+ p('План/сценарий на 2027-04 с source cutoff 2026-07-31 разбирает механизм сравнимости, а не удачную оптимизацию. Конкретная проблема: два build запускают с разными входами и называют разницу временем сборки. У одного есть предварительно подготовленный workspace, у другого меняется dependency graph; один публикует часть output, другой считает полный каталог. Цена такого сравнения — ложная причинность: следующий change закрепляет случайное преимущество и усложняет откат.'),
+ p('Вторая проблема — неразличённые уровни evidence. Config, CI trace, cache state, artifact manifest и итоговый вывод выглядят как один отчёт, хотя отвечают на разные вопросы. В будущем апреле 2027 они не собраны: нет run, CI artifact, timing, cache hit, bundle size, migration, release или production outcome. Позитивный результат здесь запрещён; evaluator может вернуть только synthetic-plan-hand-off, а productionEffect остаётся not-attempted.'),
+ h2('Сравнимость сначала строят, затем измеряют'),
+ p('Сравнимость — не обещание равных чисел и не настройка одного инструмента. Это явное правило, по которому future owner сможет решить, относятся ли две записи к одному вопросу. В минимальном виде правило связывает форму входа, границу config, способ получения зависимостей, состояние cache, runner context, состав output и метод фиксации artefact. Отсутствие одного звена не делает результат «приблизительно честным»: оно меняет смысл inference.'),
+ p('Особенно опасно оставлять скрытыми параметры, которые кажутся техническим шумом. Смена target, optimisation mode, lockfile, source map policy или restore key может изменить ход обработки раньше, чем появится финальный файл. Перечисление не означает, что такие значения существуют в P110; это список классов вопросов для будущей методики. Именно поэтому fixed literal не содержит правдоподобных flags. Он хранит только named placeholders и not-collected.'),
+ figure('/assets/editorial/2027/build-evolution-2027-comparable-conditions-matrix.svg', 'Русская матрица сопоставляет вход, configuration, cache, CI и artifact с требуемой future provenance; красная строка останавливает скрытое условие и положительный вывод.', 'Матрица показывает логическую полноту будущего сравнения. В ячейках нет реальных значений, timing, cache hit или размера bundle.'),
+ table('Матрица inference без измеренного результата', ['Слой', 'Что можно назвать сейчас', 'Какое evidence потребуется потом', 'Какой вывод пока запрещён'], [
+ ['Input', 'synthetic application shape', 'зафиксированная форма входа', 'что приложения равны'],
+ ['Configuration', 'два named placeholders', 'полная provenance параметров', 'что замена эквивалентна'],
+ ['Cache', 'state not-collected', 'ключ, источник, холодный/тёплый режим', 'что был hit или выигрыш'],
+ ['CI', 'comparable condition question', 'runner и порядок шагов', 'что timing сравним'],
+ ['Artifact', 'artifact question', 'manifest и метод подсчёта', 'что bundle меньше или лучше'],
+ ]),
+ h2('Кэш меняет эксперимент, а не только скорость'),
+ p('В разговоре о CI cache часто появляется как уже понятная оптимизация. Для inference этого недостаточно. Нужно отделить cache, который ускоряет повторное получение зависимостей, от промежуточных результатов transform и от сохранённого output. Их границы, ключи и invalidation могут быть различны. Без этой карты фраза «второй запуск быстрее» ничего не говорит: возможно, менялась не configuration, а история рабочего каталога или очередь runner.'),
+ p('Даже корректно названный cache condition не позволяет перейти к будущему результату. Сначала owner должен решить, сравниваются холодные состояния, повторные состояния или оба режима; затем описать одинаковый порядок подготовки для двух сторон. Это не приглашение выполнить команды в данном draft. Это требование к будущему доказательству. P110 не создаёт key, directory, workflow или результат restore, поэтому evaluator stop-веткой закрывает любое значение, отличное от not-collected.'),
+ h2('CI timing не существует без границы времени'),
+ p('Время job нельзя считать цельным свойством сборщика. В него могут войти ожидание, checkout, сеть, dependency install, transform, архивирование, публикация, cleanup и отчётность. Полезный будущий протокол обязан назвать начало и конец отсчёта, а также перечислить, какие внешние фазы исключены. Иначе одна сторона получает полное wall-clock время, а другая — только узкий участок, и цифры становятся риторикой.'),
+ p('Здесь нельзя улучшить текст выдуманным диапазоном или synthetic milliseconds. Synthetic shape допустима только как форма поля, не как измеренное значение. Она может сказать «будущий timing evidence должен иметь границу», но не «ожидаемая сборка займёт X». Такое различение важно: правдоподобная цифра переживёт draft и станет ложным требованием. Отсутствующее число менее опасно, потому что не имитирует наблюдение.'),
+ h2('Artifact сравнивают по составу и provenance'),
+ p('Artifact — не синоним одного zip, каталога или bundle. Прежде чем говорить о размере, будущей проверке надо выбрать состав: входят ли HTML, JavaScript, CSS, map, статические ресурсы, generated metadata и compression representation. Затем необходим способ связать состав с configuration и input. SLSA provenance полезна как терминология происхождения, но сама спецификация не сообщает, что у P110 существовал или проверялся какой-либо attestation.'),
+ p('Из этого следует предел inference. Одинаковый filename не подтверждает одинаковое содержимое; разный hash не сообщает причину; один manifest без условия не доказывает преимущество. Будущий owner может получить материал, который опровергает исходную гипотезу или показывает, что вопросы несопоставимы. Это хороший результат evidence scope, но он не является результатом текущего плана. Current output остаётся передачей необходимого question contract.'),
+ h2('Почему один diff не образует контроль'),
+ p('Configuration diff полезен как указатель, но не как контроль эксперимента. Он показывает, что текст параметров отличается, однако не гарантирует одинаковую зависимостную базу, порядок подготовки, окружение runner или состав потребляемого output. Обратное также верно: небольшой diff может скрыть существенную разницу за внешним default либо за изменённой версией зависимости. Поэтому будущему reviewer потребуется не только сравнение файлов, а карта того, откуда каждая существенная настройка пришла и на какую часть процесса могла воздействовать.'),
+ p('Эта карта не обязана быть огромной. Её ценность в том, что она даёт каждому утверждению адрес: вход относится к source boundary, cache — к состоянию повторного использования, CI — к выполнению workflow, artifact — к выходу. Если адрес отсутствует, утверждение нельзя усилить знаком процента или словом «стабильнее». В P110 намеренно нет таких процентов. Неопределённость остаётся явной, пока будущая работа не свяжет её с конкретным разрешённым evidence.'),
+ h2('Граница между измерением и интерпретацией'),
+ p('Даже реальное значение не говорит само за себя. Метод может сообщить длительность выбранного участка, но не объяснить, почему она изменилась; manifest может показать состав, но не указать, какой элемент приемлем для пользователя; cache event может быть записан, но не сообщить, повторится ли он в другом контексте. Будущий документ обязан отделить raw observation от interpretation и перечислить альтернативные объяснения. Этот separation нужен до того, как команда выберет язык итогового сообщения.'),
+ p('Для апрельского сценария предел ещё строже: отсутствуют и raw observation, и метод. Поэтому нельзя написать даже осторожное «вероятно». Вероятность без population, условий и полученного значения быстро становится скрытым прогнозом. Mechanism article оставляет только структуру будущего вывода: сначала доказать, что стороны отвечают на одно и то же условие, затем прочитать материал, и лишь после этого решить, имеет ли смысл объяснение.'),
+ h2('Что делать с несопоставимостью'),
+ p('Несопоставимость не следует маскировать нормализацией, которую выбрали после результата. Если один путь требует другого deployment contract или иной ownership boundary, возможно, это два независимых инженерных решения, а не кандидаты одной гонки. Тогда future owner может подготовить два отдельных decision records либо отказаться от количественного сравнения. Такой выход не означает провал сборки или плана; он сообщает, что первоначальная форма вопроса была слишком широкой.'),
+ p('Честный stop экономит и время, и доверие. Он не запрещает продолжить исследование, но запрещает превращать накопленные полуфакты в готовую рекомендацию. Именно это делает fixed validator частью редакционной дисциплины: он не знает, как собрать artefact, зато не даёт модели случайно принять отсутствие comparable condition за основание писать о прогрессе.'),
+ h2('Безопасный literal вместо скрытой методики'),
+ code("import { createFixedBuildCase, assessFixedBuildPlan } from './upgrade-2027-04.mjs';\n\nconst plan = createFixedBuildCase('missing-comparable-condition-v1');\nconsole.log(assessFixedBuildPlan(plan));\n// { status: 'stop-missing-comparable-condition', productionEffect: 'not-attempted', ... }"),
+ p('Этот runnable example доказывает только реакцию validator на известный fixed literal. JSON clone отделяет объект caller от шаблона, deep freeze защищает вложенные historicalArtifacts, а JSON-equality не позволяет тихо добавить cache assumption. Fail-closed важен не как стилистика: если условие не названо, система возвращает stop вместо попытки выбрать «разумное» default. Модуль не запускает package manager, build, cache restore, CI job или анализ файлов.'),
+ h2('Последовательность построения доказательства'),
+ ol(['Назвать один вопрос, который будет одинаков для обеих сторон, и отметить его как synthetic до authorised scope.', 'Зафиксировать две configuration boundaries без инструментальных версий, flags и файлов, которых нет в evidence.', 'Сформулировать cache states и временную границу как будущие условия, не как уже известную методику.', 'Определить состав artifact и provenance chain, не приписывая manifest или attestation текущему плану.', 'Собрать evidence только после отдельного разрешения и сохранить raw inputs рядом с методом.', 'Разрешить итоговый inference только новому owner; P110 передаёт ему вопрос, а не результат.']),
+ h2('Полная карта отказов evaluator'),
+ p('В механизме важен не только accepted branch. Case undated-future-scenario-v1 останавливает документ до обсуждения условий; missing-comparable-condition-v1 показывает, что отсутствие контрольной рамки не восполняется похожестью; hidden-configuration-or-cache-assumption-v1 не допускает молчаливой методики; disallowed-positive-result-v1 не пропускает conclusion о пользе. Такой набор не моделирует запуск и не говорит о будущем tool release. Он проверяет, что логическая форма hand-off не превратится в report после одного удачного слова.'),
+ h2('Ограничения механизма и следующий шаг'),
+ p('Механизм не заменяет performance budget, security review, reproducibility policy или договорённость команды о стоимости CI. Он не утверждает, что cache полезен, что один bundle важнее другого или что временная граница уже выбрана. Документация webpack и GitHub описывает свои механизмы, но не даёт универсальную метрику для неизвестного приложения. Тем более она не подтверждает факты про апрель 2027.'),
+ p('Следующий шаг — future evidence owner решает, достаточно ли конкретен comparable condition для отдельной работы. Если нет, честный исход — расширить или закрыть вопрос без запуска. Если да, он создаёт новый dated artefact с provenance, методикой и ограничениями, не переписывая этот draft как retrospective. Именно это разделение удерживает эволюцию сборки от подмены: сначала сравнимость, затем данные, после этого — возможно — вывод.'),
+], refs);
+
+const field = revision({ slug: 'editorial-2027-04-field-build-evolution', title: 'Эволюция frontend-сборки: передача синтетического evidence hand-off', categories: ['Frontend', 'Практика команд'], cover: '/assets/editorial/2027/build-evolution-2027-evidence-handoff-loop.svg', excerpt: 'План на апрель 2027: как передать вопрос о сборке следующему владельцу, не превратив план в фиктивный build report.', readingMinutes: 21 }, [
+ p('Этот field-текст — явный план/сценарий на 2027-04 с source cutoff 2026-07-31. Его проблема не в отсутствии ещё одного dashboard, а в опасной передаче контекста: будущая команда получает фразу «сборка стала лучше», но не получает исходный config, условие CI или состав артефакта. Цена — повторная работа и ложная операционная память. Следующий инженер вынужден либо верить легенде, либо воспроизводить решение с нуля.'),
+ p('Вторая цена возникает, когда незаполненную карточку называют полевым отчётом. Для P110 не существуют собранные historical config/CI artifacts, build timing, cache hit, bundle size, migration, tool release или результат. Очередь требует уметь мыслить их shape, но не выдумывать content. Поэтому hand-off не способен завершиться report, approval или production change: допустим только synthetic-plan-hand-off с productionEffect: not-attempted.'),
+ h2('Передавать вопрос вместе с запретами'),
+ p('Хороший hand-off короче плохого отчёта, потому что не маскирует отсутствующие данные. Он содержит дату, source cutoff, два named synthetic configuration placeholders, named comparable condition и список классов evidence, которые остаются not-collected. Рядом нужны запреты: не считать cache hit известным, не называть artifact маленьким, не объявлять миграцию завершённой. Эти отрицательные условия важны: без них новый владелец может принять форму карточки за заполненный факт.'),
+ p('Такая передача не перекладывает техническое мышление на следующего человека. Она оставляет ему проверяемую границу решения. Owner видит, что вопрос относится к сравнению configuration, а не к рейтингу инструментов; что CI и cache — потенциальные условия, а не фон; что historical artifacts запрошены, но не получены. Он может выбрать другой путь: уточнить condition, запросить разрешение, отказаться от измерения или доказать, что сравнение не нужно. Никакой из этих вариантов не задним числом делает P110 report.'),
+ figure('/assets/editorial/2027/build-evolution-2027-evidence-handoff-loop.svg', 'Русская петля передачи evidence: synthetic вопрос проходит дату, две конфигурации и comparable condition, затем либо останавливается при скрытом предположении, либо передаётся future owner без production effect.', 'Петля показывает границы hand-off. Она не изображает реальную CI задачу, config, cache restore, артефакт или итог измерения.'),
+ table('Минимальная карточка передачи без фальшивого отчёта', ['Поле', 'Допустимое состояние сейчас', 'Кому адресован вопрос', 'Недопустимая подмена'], [
+ ['Дата и cutoff', '2027-04 / 2026-07-31', 'редактору и future owner', 'недатированный сценарий'],
+ ['Стороны', 'named synthetic placeholders', 'владельцу сравнения', 'реальные flags без provenance'],
+ ['Condition', 'named, not-collected', 'автору методики', 'похожая, но другая среда'],
+ ['Исторический evidence', 'config/CI/timing/cache/artifact not-collected', 'разрешённому сборщику данных', 'вымышленный build report'],
+ ['Вывод', 'synthetic-plan-hand-off', 'следующему решающему', 'improved, migrated, released'],
+ ]),
+ h2('Synthetic shape полезна, если не похожа на данные'),
+ p('Синтетическая форма должна быть достаточно конкретной, чтобы validator мог отклонить ошибку, и достаточно пустой, чтобы не напоминать секретный production snapshot. Поэтому literal называет classes: config, ci, buildTiming, cacheHit, bundleSize. У каждого значение not-collected. Названия не сообщают формат файла, имя job, ключ cache, число байтов или миллисекунды. Это не неполнота реализации, а сознательная граница редакционного scope.'),
+ p('Не нужно лечить эту границу более реалистичными placeholders вроде вымышленных runner image, branch или JSON manifest. Они выглядят безобидно, но создают фиктивную provenance: читатель начинает искать их в системе или переносить в настоящий pipeline. Если future owner действительно получит artefact, он обязан зафиксировать его происхождение в новом разрешённом документе. Апрельский текст может дать ему вопрос «какой artefact нужен», но не адрес, имя или содержимое этого artefact.'),
+ h2('Negative branches экономят время команды'),
+ p('Fail-closed в hand-off означает, что неполный input не становится допустимым за счёт догадки. Недатированный future scenario, отсутствующий comparable condition, hidden configuration или assumed cache приводят к отдельному stop status. Это не бюрократическая кара. Каждая ветка показывает следующему owner, какую именно неопределённость нельзя перескочить. Она лучше общей фразы «нужно больше данных», потому что не приглашает заполнить пробел интуицией.'),
+ p('Отдельная ветка защищает от положительного результата. Фраза build-improved остановлена не потому, что улучшение невозможно, а потому, что не существует ни метода, ни данных, ни времени, к которым её можно привязать. В перспективе evidence может показать как выгодный, так и нейтральный или отрицательный исход. Хорошая карточка передаёт право получить любой из этих ответов; плохая заранее требует подтвердить историю про прогресс.'),
+ h2('Пример не имитирует CI'),
+ code("import { createFixedBuildCase, assessFixedBuildPlan } from './upgrade-2027-04.mjs';\n\nconst handOff = assessFixedBuildPlan(createFixedBuildCase());\nconsole.log(JSON.stringify({ next: handOff.nextAction, effect: handOff.productionEffect }));\n// {\"next\":\"give-the-named-synthetic-comparison-question-to-a-future-evidence-owner\",\"effect\":\"not-attempted\"}"),
+ p('Этот example запускается в Node как чистая проверка exports. JSON clone не позволяет одному запуску испортить fixed literal для другого, recursive deep freeze блокирует изменения во вложенных объектах, а evaluator принимает только известные cases. У него нет чтения disk, env, clock, network, CI API, cache storage или build process. Следовательно, строка в консоли не является логом pipeline; она подтверждает лишь то, что документ умеет отказать при неправильной форме hand-off.'),
+ h2('Кому принадлежит следующий вывод'),
+ p('Редактор статьи не должен незаметно стать владельцем будущего CI-эксперимента. У него нет ни контекста доступа, ни права выбирать исходные данные, ни возможности договориться о рисках публикации. Поэтому полезно адресовать hand-off не абстрактной «команде», а роли future evidence owner: человеку или группе, которым новый scope явно даст право собрать и интерпретировать материалы. Эта роль может не появиться вовсе, и план не становится от этого ошибочным.'),
+ p('Передаваемая карточка должна помочь этой роли задать первые вопросы: какой decision она обслуживает, какие данные допустимо читать, где будет храниться raw evidence, кто проверит comparison condition и какой итог считается честным, включая отказ от измерения. Ответы не вписываются заранее в значения literal. Иначе технический долг hand-off превращается в организационный долг: owner формально назначен, но уже связан неподтверждёнными предпосылками.'),
+ h2('Как не превратить документ в очередь задач'),
+ p('У подобного сценария есть соблазн закончиться списком операций: «сделать workflow», «настроить cache», «снять bundle», «опубликовать отчёт». В P110 эти глаголы опасны, потому что подразумевают существующие интеграции и согласованные права. Более точный порядок сохраняет форму вопроса: запросить authorisation, определить allowed evidence, сформировать метод, после этого собрать данные. Пока первый шаг не выполнен, следующие остаются не работой, а возможными обязанностями другого scope.'),
+ p('Это ограничение не снижает практическую ценность field-статьи. Напротив, получатель видит, что ему не нужно верить в прошлый запуск и не требуется защищать заранее обещанное улучшение. Он может начать с проверки того, существует ли вообще decision, ради которого стоит платить за доказательство. Если такого decision нет, лучший hand-off — закрыть сценарий без migration story и сохранить отсутствие production effect как честный итог.'),
+ h2('Слова, которые стоит остановить до публикации'),
+ p('Перед передачей полезно вычеркнуть конструкции «получили cache hit», «сократили build», «уменьшили bundle», «перешли на новую сборку» и «подтвердили эффект». В реальном отчёте эти слова могут быть точными при наличии датированных материалов. Здесь они делают из будущего сценария вымышленное прошлое. Безопасные формулировки скучнее: «требуется определить», «не собрано», «нужна provenance», «может быть передано». Но именно они не дают следующему читателю спутать план с данными.'),
+ p('Тон также важен для практики команды. Не нужно оправдывать отсутствие результата длинными оговорками или обвинять будущий owner в задержке. Достаточно назвать границу и следующее решение. Такой текст не делает неопределённость красивой; он делает её видимой и ограниченной. Если позже появится проверяемый материал, он будет сильнее именно потому, что не вырос из текста, где conclusion уже был объявлен до evidence.'),
+ h2('Порядок передачи будущему evidence owner'),
+ ol(['Проверить temporal boundary: только 2027-04 и cutoff 2026-07-31 допускают будущий сценарий.', 'Проверить, что левая и правая стороны названы как synthetic configuration, а не как реальные конфиги.', 'Остановить карточку, если comparable condition не имеет имени или скрыта за словом «аналогично».', 'Оставить каждый класс historical evidence в not-collected до нового authorisation и provenance.', 'Не допускать слова о hit, timing, size, migration или release как о свершившихся событиях.', 'Передать named question следующему owner вместе с причиной, почему productionEffect не пытались получить.']),
+ h2('Ограничения и следующий шаг'),
+ p('Полевой hand-off не создаёт ticket, job, access, owner assignment, CI workflow или хранилище артефактов. Он не оценивает, кто будет оплачивать runner, допустим ли доступ к историческому config, можно ли публиковать manifest и нужен ли компании сам эксперимент. Официальные источники помогают не путать caching и provenance с фактами, но не снимают эти организационные вопросы. У текста нет скрытого обещания, что данные станут доступны в апреле 2027.'),
+ p('Следующий шаг прост и ограничен: будущий owner принимает или отклоняет evidence question в новом scope. При принятии он обязан сформировать отдельный dated record с реальными inputs, методикой, raw artifacts и выводом, включая возможность отсутствия улучшения. При отклонении hand-off остаётся корректным незавершённым планом. В обоих случаях P110 сохраняет полезное: он не превращает красивую историю об эволюции frontend-сборки в ложное свидетельство.'),
+], refs);
+
+export const revisions = deepFreeze([practice, mechanism, field]);
+export function verifyRevisionsAgainstFixture() {
+ const fixture = runFixedBuildFixture();
+ const articleChecks = revisions.map((item) => { const text = bodyText(item.contentHtml); return text.length >= 5000 && text.length <= 15000 && /(цен[аы]|стоимост|потрат|повторн)/i.test(text.slice(0, 1100)) && /