rewrite 2026-09 and 2027 articles for reader-facing quality
Build and deploy / deploy (push) Successful in 18s

This commit is contained in:
2026-07-31 22:26:56 +03:00
parent 3bfc3f21c4
commit 440c8721dc
69 changed files with 4424 additions and 3533 deletions
+37 -79
View File
@@ -1,96 +1,54 @@
# P110 — April 2027: «Эволюция frontend-сборки»
# Апрель 2027 — редакторское ревью после переписывания
## Статус, 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.
- `editorial-2027-04-practice-build-evolution` — 5 508 знаков основного текста;
- `editorial-2027-04-mechanism-build-evolution` — 5 074 знака;
- `editorial-2027-04-field-build-evolution` — 5 488 знаков.
Очередь P110 запрашивает исторические config/CI-артефакты и одинаковый build-замер. В этом наборе они смоделированы только как именованные поля synthetic shape со значением `not-collected`; это не данные, не фрагменты системы и не скрытая методика. Единственный разрешённый положительный output — `synthetic-plan-hand-off`, всегда с `productionEffect: not-attempted`.
Stable slugs сохранены. Три статьи разбирают разные вопросы frontend-сборки: сравнимость замеров, ключ cache и поиск источника роста bundle.
Изменены только:
## Проход 1 — факты, техника, пример и источники
- `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`
- **Practice.** До: сравнение сборок сводилось к «новый инструмент быстрее». После: `compareBuildRuns()` сначала проверяет `inputFingerprint`, затем считает delta времени и размера. Причина: пример не выдаёт разные входы за один benchmark.
- **Mechanism.** До: cache описывался как общий ускоритель. После: `makeDependencyCacheKey()` включает lockfile, config, runtime и source digest и показывает invalidation при изменении lockfile. Причина: cache получил явный контракт входов.
- **Field.** До: рост bundle предлагалось оценивать по размеру одного файла. После: `summarizeBundleDiff()` сравнивает esbuild-подобные `inputs`, сортирует delta bytes и ведёт к chunk/source map/network. Причина: код показывает конкретный путь от симптома к модулю.
Registry, README, `articles.json`, app, queue, P102–P109 и Git state не менялись.
Источники проверены HTTP 200 и применены с границами:
## Pinned official primary sources
- [webpack Caching Guide](https://webpack.js.org/guides/caching/) — webpack 5 guide, проверен 31 July 2026; contenthash/deterministic ids и условия воспроизводимого output; не даёт данных конкретного проекта.
- [webpack cache configuration](https://webpack.js.org/configuration/cache/) — webpack 5 reference, проверен 31 July 2026; memory/filesystem cache; не знает состояние конкретного CI.
- [esbuild Metafile](https://esbuild.github.io/api/#metafile) — API documentation, проверена 31 July 2026; JSON inputs/outputs/bytes; не измеряет браузерную доставку.
- [Vite dependency pre-bundling](https://vite.dev/guide/dep-pre-bundling.html) — официальное руководство, проверено 31 July 2026; lockfile, config, environment и linked dependency invalidation; относится к Vite optimizer.
- [MDN SourceMap header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/SourceMap) — MDN Web Docs, page modified 21 November 2025; связь DevTools с картой; не подтверждает доступность карты и скорость ресурса.
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.
## Проход 2 — структура, голос, плотность и мета-лексика
Все источники первичные, official, фиксированы не позднее cutoff. Никаких источников о будущих релизах инструментов, будущем CI или будущих build results не использовано.
- **Practice.** До: первым шёл секундомер. После: первые два абзаца называют разные input/config/cache/output и цену ложного сравнения, а секундомер появляется после условий. Причина: действие поставлено после постановки проблемы.
- **Mechanism.** До: cache hit звучал как доказательство корректного результата. После: отдельные разделы разводят key, invalidation, hit/miss и проверку содержимого output. Причина: техническая речь называет, что именно повторяется.
- **Field.** До: «bundle вырос» сразу вёл к удалению библиотеки. После: текст проходит metafile diff, input, chunk, source map и browser measurement. Причина: каждый шаг сужает место причины и не смешивает bytes с latency.
## Review pass 1 — редакционный фокус, голос и temporal honesty
Запрещённая мета-лексика в body не найдена. В каждой статье есть конкретный reader-facing симптом, стоимость ошибки, объяснение механизма, runnable example, ordered steps, ограничения и следующий шаг. Общих 12-словных фрагментов с январём–мартом нет.
- **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.
## Проход 3 — визуал, мобильная читаемость, доступность и повторы
## Review pass 2 — literal safety, отсутствие фактов и fail-closed
- **Practice.** До: timeline показывал редакционную передачу конфигураций. После: SVG показывает input → config → cache → замер → output diff и красную ветку другого fingerprint. Причина: рисунок объясняет условие сравнимости.
- **Mechanism.** До: матрица содержала synthetic shape и статусы отсутствующих данных. После: строки описывают input, config, cache, runner и output; нижняя ветка останавливает несопоставимый замер. Причина: визуал служит cache/benchmark механизму.
- **Field.** До: схема показывала hand-off. После: она сравнивает два metafile, считает delta, ведёт к chunk/map/network и возвращает при несовпадении input. Причина: диаграмма соответствует `summarizeBundleDiff()`.
- `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.
SVG получили русские title/desc, доступные alt/caption и компактные подписи. Sharp 375 px проверил таблицу, стрелки, красные ветки и отсутствие обрезки. Опасные SVG-элементы и внешние ресурсы отсутствуют.
## 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(/<h2>Проверяемые источники<\\/h2>[\\s\\S]*/, '').replace(/<[^>]+>/g,' ').replace(/&(?:quot|amp|lt|gt|#039);/g,' ').toLowerCase().replace(/[^a-zа-яё0-9]+/giu,' ').trim().split(/\\s+/); const grams=a=>new Set(a.slice(0,-11).map((_,i)=>a.slice(i,i+12).join(' '))); let failed=false; for(let i=0;i<revisions.length;i++) for(let j=i+1;j<revisions.length;j++){const left=text(revisions[i].contentHtml), right=text(revisions[j].contentHtml), common=[...grams(left)].filter(x=>grams(right).has(x)); console.log(revisions[i].slug+' <> '+revisions[j].slug, 'shared-12-word-fragments='+common.length); if(common.length){failed=true;console.log(common.join('\\n'));}} if(failed) process.exitCode=1;"
```text
node --check scripts/upgrade-2027-04.mjs — PASS
node scripts/upgrade-2027-04.mjs --verify-fixture — PASS, 4/4
npm run audit:draft -- scripts/upgrade-2027-04.mjs — PASS, 3/3
локальные build/cache/bundle examples — PASS, 12/12 для всей партии
xmllint --noout для 3 SVG — PASS
SVG safety scan — PASS
Sharp resize width=375 для 3 SVG — PASS; визуально проверены timeline, matrix и bundle loop
strict cross-article scan января–апреля: 12 статей, 66 сравнений, sources excluded, code included — collisions: []
```
Результаты фактического запуска:
- Syntax: PASS, exit 0.
- Fixture и article guards: PASS `8/8`; fixture `5/5`, все три article checks `true`.
- Exact body chars, без section sources: `editorial-2027-04-practice-build-evolution` — `10846`; `editorial-2027-04-mechanism-build-evolution` — `11098`; `editorial-2027-04-field-build-evolution` — `9766`. Каждый текст находится в общем диапазоне 5k–15k и целевом 9k–13k.
- Draft audit: PASS для трёх exact slugs.
- Literal public exports: PASS. Статусы cases: `synthetic-plan-hand-off`, `stop-undated-future-scenario-or-cutoff`, `stop-missing-comparable-condition`, `stop-hidden-configuration-or-cache-assumption`, `stop-disallowed-positive-result`; `exports-frozen true true not-attempted`.
- `xmllint`: PASS без output. SVG safety scan: PASS, `rg` не нашёл запрещённый content.
- Sharp rendering и 375 px manual inspection: PASS для всех трёх SVG.
- Strict pairwise duplicate scan (code included, sources excluded): PASS. `practice <> mechanism = 0`, `practice <> field = 0`, `mechanism <> field = 0` shared 12-word fragments.
Единственный не-failing output — существующие npm warnings о неизвестных user config `store-dir`, `cache-dir`, `public-hoist-pattern`; audit прошёл, warnings не относятся к P110.
## Независимая приёмка — 2026-07-31
### Проход 1 — источники, постановка и голос
- Mutable guide links заменены на проверяемые immutable snapshots: `webpack.js.org` commit `b7fa07c852d24449e9445717c41395bdd2ca4784` и `actions/cache` v4.2.3 commit `5a3ec84eff668545956fd18022155c47e93e2684`. Оба первичных источника доступны по закреплённым путям; SLSA остаётся версионной спецификацией v1.0.
- Проверена фактическая граница claim: источники задают терминологию content hash, cache key/restore/save и provenance, но ни один не превращён в доказательство local config, CI run, cache hit, bundle size или migration.
- У всех трёх статей собственная цена ошибки и самостоятельный вопрос: сравнимость config, уровни evidence и честная передача будущему владельцу. Автор говорит о стоимости решения и воспроизводимом методе, не рекламируя bundler.
### Проход 2 — исполнимый контракт и автоматический аудит
- `node --check`, fixture `8/8`, literal export/deep-freeze check и `npm run audit:draft -- scripts/upgrade-2027-04.mjs` завершились успешно. Длины body: 10 890, 11 098 и 9 766 знаков.
- Пять fixed cases дают ровно один `synthetic-plan-hand-off` и четыре fail-closed stop-ветви; positive result и скрытая config/cache assumption не проходят. Accepted branch сохраняет `productionEffect: not-attempted`.
- `xmllint` прошёл без output; SVG safety scan не нашёл executable или external active content.
### Проход 3 — визуал, повторяемость и выпуск
- Три SVG отрендерены и просмотрены в ширине 375 px: timeline, матрица и loop сохраняют заголовки, ячейки, стрелки и stop-блоки без обрезки.
- Cross-package scan P102–P110 включает код и исключает источники. Первый запуск выявил шесть общих 12-словных фрагментов у practice runnable example с P108/P109; example переписан как отдельная boolean-проверка question contract. Повторный запуск по 27 статьям: `collisions: []`.
- В реестр добавлены только три апрельские revisions; пользовательские app/data files не включены.
Git, registry, README, `articles.json`, приложение и файлы других месяцев не изменялись.