revise November 2025 source verification articles
Build and deploy / deploy (push) Successful in 16s

This commit is contained in:
2026-07-31 18:34:41 +03:00
parent 775ae9b71a
commit def4f0a1f5
7 changed files with 616 additions and 1 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# Производство редакционных партий
На 31 июля 2026 года строгий аудит проходит 280 из 358 созданных материалов. Остальные 78 не считаются «почти готовыми»: их нужно заменить, а не косметически удлинить.
На 31 июля 2026 года строгий аудит проходит 283 из 358 созданных материалов. Остальные 75 не считаются «почти готовыми»: их нужно заменить, а не косметически удлинить.
## Одна партия
+89
View File
@@ -0,0 +1,89 @@
# P93 — ноябрь 2025: проверка источников
## Область draft-пакета
Пакет создаёт только три overlay-статьи: `editorial-2025-11-practice-research-method`, `editorial-2025-11-mechanism-research-method`, `editorial-2025-11-field-research-method`; исполняемый модуль — `web/scripts/upgrade-2025-11.mjs`. Все записи, версии, подписи, валидаторы, результаты и hand-off — fixed synthetic JavaScript literals в памяти. Модуль не читает файловую систему, сеть, часы, Git, CI, telemetry, production или user data и не производит side effect. Registry, README, Git, очередь, `articles.json`, app-код, `docs/` и чужие незавершённые изменения не затронуты.
## Исследование и временная граница
Временная граница материала — **30 ноября 2025**. Все ссылки в `sourceList` ведут к первичным или официальным неизменяемым публикациям и существовали до этой даты.
| Источник | Pin / дата | Подтверждённый факт | Явная граница применения |
| --- | --- | --- | --- |
| [W3C PROV-DM](https://www.w3.org/TR/2013/REC-prov-dm-20130430/) | W3C Recommendation, 30.04.2013, dated immutable publication | Различает entity и activity. | Это vocabulary происхождения, не автоматическая оценка истинности claim. |
| [RFC 9110, §8.8](https://www.rfc-editor.org/rfc/rfc9110.html) | RFC 9110, June 2022, immutable RFC | ETag — opaque validator selected representation; Last-Modified участвует в validation и может быть weak. | Validator не является релизной версией и не доказывает продуктовую семантику. |
| [W3C VC Data Model 2.0](https://www.w3.org/TR/2025/REC-vc-data-model-2.0-20250515/) | W3C Recommendation, 15.05.2025, dated immutable publication | Verification credential не означает evaluation truth encoded claims. | Целостность/авторство объекта не заменяют прямой evidence для вывода. |
Источники использованы только в этих узких пределах. Никакая статья не обещает, что pin, подпись, validator или журнал автоматически делают факт истинным, совместимым либо безопасным для production.
## Проход 1 — редактура проблемы, механики, таблиц и языка
Проверены первые два абзаца каждой статьи: в них есть наблюдаемая проблема, цена и действие. Практика строит карточку журнала и порядок проверки; механизм объясняет четыре уровня evidence и статусную модель; поле ведёт hand-off и обновление после нового материала. Общего развёрнутого вступления нет.
| Статья | Самостоятельный вопрос | Свой артефакт | Цена ошибки |
| --- | --- | --- | --- |
| practice | Как превратить ссылку в ограниченный claim? | лестница evidence и карточка журнала | решение привязано к изменившейся странице |
| mechanism | Почему версия, validator и подпись не равны semantic fact? | матрица статусов и переходов | неявная предпосылка в архитектурном решении |
| field | Как передать проверку дальше без пересказа? | петля research log и очередь repair | повторный поиск и исправление не той причины |
Тон М8: короткая техническая речь, symptom → cause → check → action; без рекламной риторики и универсальных обещаний. Термины раскрыты контекстом, основной текст не содержит общих оценок вместо наблюдаемых условий.
**Вердикт:** пройдено.
## Проход 2 — факты, временная граница и воспроизводимость
- `createFixedResearchRecord` возвращает только named fixed literals; произвольный объект получает `stop-unknown-fixed-record`.
- `assessFixedResearchRecord` отделяет запись с version pin от versionless note, marketing promise и семантического overreach поверх valid signature.
- `createFixedResearchLog` сохраняет `sourcePin`, `sourceVersion`, `locator`, observed artifact, scope, status и next action; положительный status означает только `evidence-ready-for-synthetic-hand-off`.
- `compareFixedResearchRecords` останавливает сравнение, если второй synthetic record не имеет version pin.
- Все snippets статей импортируют исключительно public exports и вручную выполнены: положительный record выдаёт `rfc-9110-june-2022` и `ETag: "fixed-v4"`; signature overreach даёт `hold-claim`; comparison даёт `stop-before-comparison`.
Фактологический self-check подтверждён первичными источниками: RFC 9110 определяет ETag как opaque validator selected representation; W3C PROV-DM задаёт entity/activity; W3C VC Data Model прямо исключает вывод truth claim из verification. Все references имеют visible version/date в article `sourceList`.
**Вердикт:** пройдено.
## Проход 3 — release-quality, уникальность, визуал и команды
| Статья | Основной текст без списка источников | Диаграмма | Ключевой visual outcome |
| --- | ---: | --- | --- |
| practice | **8 128** знаков | evidence ladder | видны пять ступеней и отдельный repair-route |
| mechanism | **9 169** знаков | claim-status matrix | после корректировки нет обрезанного заголовка на 375 px |
| field | **9 150** знаков | research-log loop | читабелен полный цикл question → card → pin → status → hand-off |
Cross-article check по body без блока источников: **0** одинаковых абзацев от 160 знаков и **0** общих последовательностей из 12 слов во всех трёх парах. Проверка повторов показывает, что каждая статья раскрывает тему своим способом, а не делит общий вводный блок.
Команды и результаты:
- `node --check web/scripts/upgrade-2025-11.mjs` — PASS.
- `node web/scripts/upgrade-2025-11.mjs --verify-fixture` — PASS, **11/11 assertions**.
- `npm run audit:draft -- scripts/upgrade-2025-11.mjs` — PASS: **8 128 / 9 169 / 9 150**; диапазон 5–15 тыс., table, figure, alt/caption, code, ordered sequence, минимум два source, problem/cost.
- `xmllint --noout` для трёх SVG — PASS.
- Safety scan — PASS: отсутствуют `script`, `foreignObject`, `javascript:`, `data:image` и inline event handlers.
- Sharp render всех трёх SVG на 375 px — PASS, визуально просмотрен; матрица была исправлена после первого mobile просмотра.
- `git diff --check` — PASS.
Overlay намеренно **не интегрирован**, не добавлен в registry, README не менялся, Git-коммита и push нет. Интеграция и production build остаются за независимым редактором.
## Независимый выпускной проход — 31.07.2026
Три первоисточника перепроверены на закреплённых датированных публикациях. PROV-DM подтверждает различие entity/activity; RFC 9110 — ETag как opaque validator selected representation; W3C VC Data Model 2.0 прямо отделяет verification от truth encoded claims. В статьи не добавлен ни один claim об автоматической истинности, совместимости или production effect.
При строгой независимой cross-article проверке найдено два общих 12-словных фрагмента между practice и field — они находились в повторённом code snippet. Чтобы правило не имело технических исключений, field переведён на отдельный публичный `createFixedResearchLogById`; после этого полная проверка HTML-body, включая code, даёт 0 одинаковых абзацев от 160 знаков и 0 общих 12-словных фрагментов. Snippet буквально выполнен, fixture остаётся 11/11. Полевая статья после правки — 9 150 знаков.
Все тексты прочитаны целиком, три SVG повторно отрендерены Sharp на 375 px и читаемы. Финальный выпускной прогон:
~~~text
node --check scripts/upgrade-2025-11.mjs
PASS
node scripts/upgrade-2025-11.mjs --verify-fixture
PASS fixture: 11/11 assertions
npm run audit:draft -- scripts/upgrade-2025-11.mjs
PASS: 8128 / 9169 / 9150 body chars
npm run audit:articles -- <3 November slugs>
PASS: each article has 1 table, 1 code example and 1 figure
~~~
После независимой приёмки overlay подключён в `editorial-revisions.mjs` после October 2025. Журнал и все examples остаются fixed in-memory models: они не создают сетевого исследования, не проверяют настоящие подписи и не принимают production решение.
+2
View File
@@ -89,6 +89,7 @@ import { revisions as july2025Revisions } from '../scripts/upgrade-2025-07.mjs';
import { revisions as august2025Revisions } from '../scripts/upgrade-2025-08.mjs';
import { revisions as september2025Revisions } from '../scripts/upgrade-2025-09.mjs';
import { revisions as october2025Revisions } from '../scripts/upgrade-2025-10.mjs';
import { revisions as november2025Revisions } from '../scripts/upgrade-2025-11.mjs';
// This layer replaces archived source entries without losing their stable slug and date.
export const editorialRevisions = [
@@ -183,4 +184,5 @@ export const editorialRevisions = [
...august2025Revisions,
...september2025Revisions,
...october2025Revisions,
...november2025Revisions,
];
@@ -0,0 +1,66 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 940" role="img" aria-labelledby="title desc">
<title id="title">Матрица статусов утверждения</title>
<desc id="desc">Таблица связывает наличие версии, наблюдаемого артефакта и scope со статусом claim и следующим действием.</desc>
<rect width="1200" height="940" fill="#f7f3eb"/>
<rect x="54" y="44" width="1092" height="126" rx="26" fill="#163a4d"/>
<text x="96" y="100" fill="#ffffff" font-family="Arial, sans-serif" font-size="42" font-weight="700">Статус claim выбирает действие</text>
<text x="96" y="142" fill="#cce7ed" font-family="Arial, sans-serif" font-size="27">не оценивает человека и не ставит рейтинг сайту</text>
<rect x="54" y="212" width="1092" height="92" fill="#254f63"/>
<text x="90" y="268" fill="#ffffff" font-family="Arial, sans-serif" font-size="30" font-weight="700">Версия / pin</text>
<text x="360" y="268" fill="#ffffff" font-family="Arial, sans-serif" font-size="30" font-weight="700">Artifact + scope</text>
<text x="694" y="268" fill="#ffffff" font-family="Arial, sans-serif" font-size="30" font-weight="700">Статус</text>
<text x="936" y="268" fill="#ffffff" font-family="Arial, sans-serif" font-size="30" font-weight="700">Дальше</text>
<rect x="54" y="304" width="1092" height="136" fill="#d9ead6"/>
<line x1="330" y1="304" x2="330" y2="848" stroke="#ffffff" stroke-width="4"/>
<line x1="664" y1="304" x2="664" y2="848" stroke="#ffffff" stroke-width="4"/>
<line x1="912" y1="304" x2="912" y2="848" stroke="#ffffff" stroke-width="4"/>
<text x="90" y="359" fill="#19482b" font-family="Arial, sans-serif" font-size="29" font-weight="700">есть</text>
<text x="90" y="399" fill="#31593a" font-family="Arial, sans-serif" font-size="24">immutable source</text>
<text x="364" y="359" fill="#19482b" font-family="Arial, sans-serif" font-size="29" font-weight="700">оба есть</text>
<text x="364" y="399" fill="#31593a" font-family="Arial, sans-serif" font-size="24">locator читаем</text>
<text x="694" y="359" fill="#19482b" font-family="Arial, sans-serif" font-size="27" font-weight="700">ready</text>
<text x="694" y="399" fill="#31593a" font-family="Arial, sans-serif" font-size="23">scoped hand-off</text>
<text x="942" y="359" fill="#19482b" font-family="Arial, sans-serif" font-size="27" font-weight="700">передать</text>
<text x="942" y="399" fill="#31593a" font-family="Arial, sans-serif" font-size="23">с границей</text>
<rect x="54" y="440" width="1092" height="136" fill="#f0e8c7"/>
<line x1="330" y1="440" x2="330" y2="848" stroke="#ffffff" stroke-width="4"/>
<line x1="664" y1="440" x2="664" y2="848" stroke="#ffffff" stroke-width="4"/>
<line x1="912" y1="440" x2="912" y2="848" stroke="#ffffff" stroke-width="4"/>
<text x="90" y="495" fill="#5e4e1c" font-family="Arial, sans-serif" font-size="29" font-weight="700">нет</text>
<text x="90" y="535" fill="#6d5d2d" font-family="Arial, sans-serif" font-size="24">только current URL</text>
<text x="364" y="495" fill="#5e4e1c" font-family="Arial, sans-serif" font-size="29" font-weight="700">частично</text>
<text x="364" y="535" fill="#6d5d2d" font-family="Arial, sans-serif" font-size="24">есть пересказ</text>
<text x="694" y="495" fill="#5e4e1c" font-family="Arial, sans-serif" font-size="27" font-weight="700">repair</text>
<text x="694" y="535" fill="#6d5d2d" font-family="Arial, sans-serif" font-size="23">source pin</text>
<text x="942" y="495" fill="#5e4e1c" font-family="Arial, sans-serif" font-size="27" font-weight="700">найти</text>
<text x="942" y="535" fill="#6d5d2d" font-family="Arial, sans-serif" font-size="23">primary pin</text>
<rect x="54" y="576" width="1092" height="136" fill="#eadce9"/>
<line x1="330" y1="576" x2="330" y2="848" stroke="#ffffff" stroke-width="4"/>
<line x1="664" y1="576" x2="664" y2="848" stroke="#ffffff" stroke-width="4"/>
<line x1="912" y1="576" x2="912" y2="848" stroke="#ffffff" stroke-width="4"/>
<text x="90" y="631" fill="#482740" font-family="Arial, sans-serif" font-size="29" font-weight="700">есть</text>
<text x="90" y="671" fill="#5b4056" font-family="Arial, sans-serif" font-size="24">signature / tag</text>
<text x="364" y="631" fill="#482740" font-family="Arial, sans-serif" font-size="29" font-weight="700">есть</text>
<text x="364" y="671" fill="#5b4056" font-family="Arial, sans-serif" font-size="24">но вывод шире</text>
<text x="694" y="631" fill="#482740" font-family="Arial, sans-serif" font-size="27" font-weight="700">hold</text>
<text x="694" y="671" fill="#5b4056" font-family="Arial, sans-serif" font-size="23">claim</text>
<text x="942" y="631" fill="#482740" font-family="Arial, sans-serif" font-size="27" font-weight="700">сузить</text>
<text x="942" y="671" fill="#5b4056" font-family="Arial, sans-serif" font-size="23">claim</text>
<rect x="54" y="712" width="1092" height="136" fill="#f1d8d4"/>
<line x1="330" y1="712" x2="330" y2="848" stroke="#ffffff" stroke-width="4"/>
<line x1="664" y1="712" x2="664" y2="848" stroke="#ffffff" stroke-width="4"/>
<line x1="912" y1="712" x2="912" y2="848" stroke="#ffffff" stroke-width="4"/>
<text x="90" y="767" fill="#742823" font-family="Arial, sans-serif" font-size="29" font-weight="700">неважно</text>
<text x="90" y="807" fill="#7e4039" font-family="Arial, sans-serif" font-size="24">есть slogan</text>
<text x="364" y="767" fill="#742823" font-family="Arial, sans-serif" font-size="29" font-weight="700">нет</text>
<text x="364" y="807" fill="#7e4039" font-family="Arial, sans-serif" font-size="24">только promise</text>
<text x="694" y="767" fill="#742823" font-family="Arial, sans-serif" font-size="27" font-weight="700">stop</text>
<text x="694" y="807" fill="#7e4039" font-family="Arial, sans-serif" font-size="23">marketing</text>
<text x="942" y="767" fill="#742823" font-family="Arial, sans-serif" font-size="27" font-weight="700">заменить</text>
<text x="942" y="807" fill="#7e4039" font-family="Arial, sans-serif" font-size="23">на факт</text>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

@@ -0,0 +1,47 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1340" role="img" aria-labelledby="title desc">
<title id="title">Лестница проверки источника</title>
<desc id="desc">Пять ступеней от ссылки к ограниченному hand-off и красная ветка ремонта, если нет версии источника.</desc>
<rect width="1200" height="1340" fill="#f7f3eb"/>
<rect x="60" y="54" width="1080" height="134" rx="28" fill="#163a4d"/>
<text x="112" y="114" fill="#ffffff" font-family="Arial, sans-serif" font-size="44" font-weight="700">Проверка источника</text>
<text x="112" y="158" fill="#cce7ed" font-family="Arial, sans-serif" font-size="28">ссылка становится основанием только после наблюдения и границы</text>
<line x1="600" y1="300" x2="600" y2="1124" stroke="#4d7b86" stroke-width="10" stroke-linecap="round"/>
<polygon points="600,1170 576,1122 624,1122" fill="#4d7b86"/>
<rect x="160" y="242" width="880" height="144" rx="24" fill="#d6e8e4" stroke="#2e6b68" stroke-width="4"/>
<circle cx="224" cy="314" r="36" fill="#2e6b68"/>
<text x="224" y="326" text-anchor="middle" fill="#ffffff" font-family="Arial, sans-serif" font-size="34" font-weight="700">1</text>
<text x="286" y="300" fill="#17313a" font-family="Arial, sans-serif" font-size="38" font-weight="700">Ссылка</text>
<text x="286" y="344" fill="#355661" font-family="Arial, sans-serif" font-size="28">Маршрут к материалу, но ещё не доказательство.</text>
<rect x="130" y="430" width="940" height="144" rx="24" fill="#dfe9f0" stroke="#27617e" stroke-width="4"/>
<circle cx="194" cy="502" r="36" fill="#27617e"/>
<text x="194" y="514" text-anchor="middle" fill="#ffffff" font-family="Arial, sans-serif" font-size="34" font-weight="700">2</text>
<text x="256" y="488" fill="#17313a" font-family="Arial, sans-serif" font-size="38" font-weight="700">Pin и версия</text>
<text x="256" y="532" fill="#355661" font-family="Arial, sans-serif" font-size="28">Что именно можно открыть повторно на границе даты.</text>
<rect x="100" y="618" width="1000" height="144" rx="24" fill="#e9e4d1" stroke="#8a6a24" stroke-width="4"/>
<circle cx="164" cy="690" r="36" fill="#8a6a24"/>
<text x="164" y="702" text-anchor="middle" fill="#ffffff" font-family="Arial, sans-serif" font-size="34" font-weight="700">3</text>
<text x="226" y="676" fill="#3e3216" font-family="Arial, sans-serif" font-size="38" font-weight="700">Наблюдаемый artifact</text>
<text x="226" y="720" fill="#5d4c28" font-family="Arial, sans-serif" font-size="28">Section, ответ, таблица или literal, а не общий пересказ.</text>
<rect x="130" y="806" width="940" height="144" rx="24" fill="#eadce9" stroke="#74486d" stroke-width="4"/>
<circle cx="194" cy="878" r="36" fill="#74486d"/>
<text x="194" y="890" text-anchor="middle" fill="#ffffff" font-family="Arial, sans-serif" font-size="34" font-weight="700">4</text>
<text x="256" y="864" fill="#3d2038" font-family="Arial, sans-serif" font-size="38" font-weight="700">Scope утверждения</text>
<text x="256" y="908" fill="#5a3c55" font-family="Arial, sans-serif" font-size="28">Где факт верен и какой перенос запрещён.</text>
<rect x="160" y="994" width="880" height="144" rx="24" fill="#d8ead6" stroke="#397044" stroke-width="4"/>
<circle cx="224" cy="1066" r="36" fill="#397044"/>
<text x="224" y="1078" text-anchor="middle" fill="#ffffff" font-family="Arial, sans-serif" font-size="34" font-weight="700">5</text>
<text x="286" y="1052" fill="#173a20" font-family="Arial, sans-serif" font-size="38" font-weight="700">Ограниченный hand-off</text>
<text x="286" y="1096" fill="#31593a" font-family="Arial, sans-serif" font-size="28">Следующий шаг назван; неизвестное осталось видимым.</text>
<path d="M130 502 H76 V1236 H380" fill="none" stroke="#a23d39" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<polygon points="422,1236 374,1214 374,1258" fill="#a23d39"/>
<rect x="400" y="1174" width="650" height="120" rx="22" fill="#f2d8d4" stroke="#a23d39" stroke-width="4"/>
<text x="438" y="1223" fill="#72221f" font-family="Arial, sans-serif" font-size="34" font-weight="700">Нет pin или версии → repair</text>
<text x="438" y="1261" fill="#72221f" font-family="Arial, sans-serif" font-size="25">Не подменять пробел новым пересказом.</text>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

@@ -0,0 +1,50 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1060" role="img" aria-labelledby="title desc">
<title id="title">Петля исследовательского журнала</title>
<desc id="desc">Цикл показывает путь от вопроса к закреплённому наблюдению, статусу и hand-off, а также возврат для ремонта источника.</desc>
<rect width="1200" height="1060" fill="#f7f3eb"/>
<rect x="54" y="42" width="1092" height="132" rx="28" fill="#163a4d"/>
<text x="98" y="100" fill="#ffffff" font-family="Arial, sans-serif" font-size="42" font-weight="700">Петля исследовательского журнала</text>
<text x="98" y="143" fill="#cce7ed" font-family="Arial, sans-serif" font-size="27">новый факт создаёт новую строку, а не переписывает прошлое чтение</text>
<path d="M388 348 C552 248 720 248 842 348" fill="none" stroke="#4d7b86" stroke-width="10" stroke-linecap="round"/>
<polygon points="886,390 834,365 861,326" fill="#4d7b86"/>
<path d="M952 502 C1024 646 918 790 746 824" fill="none" stroke="#4d7b86" stroke-width="10" stroke-linecap="round"/>
<polygon points="690,834 748,847 734,801" fill="#4d7b86"/>
<path d="M470 826 C280 786 190 636 258 498" fill="none" stroke="#4d7b86" stroke-width="10" stroke-linecap="round"/>
<polygon points="288,447 276,504 320,486" fill="#4d7b86"/>
<path d="M346 390 C372 338 408 300 454 276" fill="none" stroke="#4d7b86" stroke-width="10" stroke-linecap="round"/>
<polygon points="500,260 448,258 469,301" fill="#4d7b86"/>
<rect x="96" y="360" width="290" height="164" rx="28" fill="#dcecf0" stroke="#27617e" stroke-width="4"/>
<text x="136" y="420" fill="#173a4a" font-family="Arial, sans-serif" font-size="36" font-weight="700">1. Вопрос</text>
<text x="136" y="462" fill="#365b69" font-family="Arial, sans-serif" font-size="25">Какое решение</text>
<text x="136" y="496" fill="#365b69" font-family="Arial, sans-serif" font-size="25">зависит от claim?</text>
<rect x="456" y="212" width="308" height="164" rx="28" fill="#e9e4d1" stroke="#8a6a24" stroke-width="4"/>
<text x="496" y="272" fill="#3e3216" font-family="Arial, sans-serif" font-size="36" font-weight="700">2. Card</text>
<text x="496" y="314" fill="#5d4c28" font-family="Arial, sans-serif" font-size="25">statement + scope</text>
<text x="496" y="348" fill="#5d4c28" font-family="Arial, sans-serif" font-size="25">дата границы</text>
<rect x="816" y="360" width="288" height="164" rx="28" fill="#d8ead6" stroke="#397044" stroke-width="4"/>
<text x="852" y="420" fill="#173a20" font-family="Arial, sans-serif" font-size="34" font-weight="700">3. Pin</text>
<text x="852" y="462" fill="#31593a" font-family="Arial, sans-serif" font-size="25">version + locator</text>
<text x="852" y="496" fill="#31593a" font-family="Arial, sans-serif" font-size="25">artifact</text>
<rect x="756" y="724" width="348" height="164" rx="28" fill="#eadce9" stroke="#74486d" stroke-width="4"/>
<text x="796" y="784" fill="#3d2038" font-family="Arial, sans-serif" font-size="35" font-weight="700">4. Status</text>
<text x="796" y="826" fill="#5a3c55" font-family="Arial, sans-serif" font-size="25">ready / repair</text>
<text x="796" y="860" fill="#5a3c55" font-family="Arial, sans-serif" font-size="25">или hold</text>
<rect x="270" y="724" width="360" height="164" rx="28" fill="#d6e8e4" stroke="#2e6b68" stroke-width="4"/>
<text x="310" y="784" fill="#17313a" font-family="Arial, sans-serif" font-size="35" font-weight="700">5. Hand-off</text>
<text x="310" y="826" fill="#355661" font-family="Arial, sans-serif" font-size="25">следующий шаг</text>
<text x="310" y="860" fill="#355661" font-family="Arial, sans-serif" font-size="25">с ограничением</text>
<path d="M960 524 V652 H1108" fill="none" stroke="#a23d39" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<polygon points="1140,652 1094,630 1094,674" fill="#a23d39"/>
<rect x="776" y="594" width="330" height="102" rx="20" fill="#f2d8d4" stroke="#a23d39" stroke-width="4"/>
<text x="806" y="638" fill="#72221f" font-family="Arial, sans-serif" font-size="29" font-weight="700">Нет version?</text>
<text x="806" y="674" fill="#72221f" font-family="Arial, sans-serif" font-size="23">Вернуться в repair.</text>
<text x="600" y="1000" text-anchor="middle" fill="#365b69" font-family="Arial, sans-serif" font-size="27">Журнал хранит путь вывода, а не маскирует неизвестное ссылкой.</text>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

+361
View File
@@ -0,0 +1,361 @@
function escapeHtml(value) { return String(value).replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#039;'); }
const p = (text) => '<p>' + text + '</p>';
const h2 = (text) => '<h2>' + text + '</h2>';
const code = (text) => '<pre><code>' + escapeHtml(text) + '</code></pre>';
const ol = (items) => '<ol>' + items.map((item) => '<li>' + item + '</li>').join('') + '</ol>';
const figure = (src, alt, caption) => '<figure><img src="' + src + '" alt="' + alt + '" loading="lazy" /><figcaption>' + caption + '</figcaption></figure>';
const table = (caption, headers, rows) => '<div class="table-scroll"><table><caption>' + caption + '</caption><thead><tr>' + headers.map((item) => '<th scope="col">' + item + '</th>').join('') + '</tr></thead><tbody>' + rows.map((row) => '<tr>' + row.map((item) => '<td>' + item + '</td>').join('') + '</tr>').join('') + '</tbody></table></div>';
function plainText(content) { return content.replace(/<[^>]+>/g, ' ').replace(/&(?:quot|amp|lt|gt|#039);/g, ' ').replace(/\s+/g, ' ').trim(); }
function bodyText(content) { return plainText(content.replace(/<h2>Проверяемые источники<\/h2>[\s\S]*?(?=<h2>|$)/, '')); }
function deepFreeze(value) { if (value && typeof value === 'object' && !Object.isFrozen(value)) { Object.values(value).forEach(deepFreeze); Object.freeze(value); } return value; }
function cloneFixed(value) { return JSON.parse(JSON.stringify(value)); }
const REFERENCES = deepFreeze({
prov: {
title: 'W3C PROV-DM: The PROV Data Model',
url: 'https://www.w3.org/TR/2013/REC-prov-dm-20130430/',
version: 'W3C Recommendation, 30 April 2013, dated immutable publication',
},
http: {
title: 'RFC 9110: HTTP Semantics, §8.8 validator fields',
url: 'https://www.rfc-editor.org/rfc/rfc9110.html',
version: 'RFC 9110, June 2022, immutable RFC publication',
},
vc: {
title: 'W3C Verifiable Credentials Data Model v2.0',
url: 'https://www.w3.org/TR/2025/REC-vc-data-model-2.0-20250515/',
version: 'W3C Recommendation, 15 May 2025, dated immutable publication',
},
});
function sourceList(entries) {
return '<ul>' + entries.map(({ key, use, boundary }) => {
const reference = REFERENCES[key];
return '<li><a href="' + reference.url + '" target="_blank" rel="noopener noreferrer">' + escapeHtml(reference.title) + '</a> — версия: ' + escapeHtml(reference.version) + '. ' + escapeHtml(use) + ' Граница: ' + escapeHtml(boundary) + '</li>';
}).join('') + '</ul>';
}
const FIXED_RESEARCH_RECORDS = deepFreeze({
'pinned-representation-v1': {
id: 'pinned-representation-v1',
statement: 'В fixed synthetic source-card поле validator относится к выбранному представлению fixed-v4.',
claimClass: 'observed-representation',
scope: 'one fixed card, one named representation, no behaviour outside the fixture',
source: { kind: 'official-standard', pin: 'rfc-9110-june-2022', version: 'RFC 9110, June 2022', locator: '§8.8.3', publisher: 'fixed-ietf-card' },
observation: { representation: 'fixed-v4', artifact: 'ETag: "fixed-v4"', method: 'literal comparison in memory', reproducible: true },
conclusion: 'representation-fact-only',
},
'versionless-compatibility-v1': {
id: 'versionless-compatibility-v1',
statement: 'Fixed note says that an unnamed release supports a synthetic feature.',
claimClass: 'observed-representation',
scope: 'one fixed note with intentionally missing version pin',
source: { kind: 'mutable-page', pin: '', version: '', locator: 'fixed-note', publisher: 'fixed-publisher' },
observation: { representation: 'fixed-current-page', artifact: 'support: maybe', method: 'literal comparison in memory', reproducible: true },
conclusion: 'compatibility-not-established',
},
'marketing-outcome-v1': {
id: 'marketing-outcome-v1',
statement: 'Fixed landing copy promises that a synthetic workflow makes every research claim trustworthy.',
claimClass: 'marketing-promise',
scope: 'one synthetic slogan, not a measured result',
source: { kind: 'landing-copy', pin: 'fixed-copy-r1', version: 'fixed copy r1', locator: 'headline', publisher: 'fixed-vendor' },
observation: { representation: 'fixed-copy-r1', artifact: 'trust every claim', method: 'literal comparison in memory', reproducible: true },
conclusion: 'outcome-not-established',
},
'signature-overreach-v1': {
id: 'signature-overreach-v1',
statement: 'A valid fixed signature proves that every semantic claim in the fixed document is true.',
claimClass: 'semantic-conclusion',
scope: 'a cryptographically protected synthetic representation only',
source: { kind: 'dated-standard', pin: 'vc-data-model-2-2025-05-15', version: 'W3C VC Data Model 2.0, 15 May 2025', locator: 'definition of verification', publisher: 'fixed-w3c-card' },
observation: { representation: 'fixed-credential-r2', artifact: 'signature: valid', method: 'literal comparison in memory', reproducible: true },
conclusion: 'truth-not-established',
},
});
function isKnownFixedRecord(record) {
return Object.values(FIXED_RESEARCH_RECORDS).some((candidate) => JSON.stringify(candidate) === JSON.stringify(record));
}
export function createFixedResearchRecord(id) {
const record = FIXED_RESEARCH_RECORDS[id];
return record ? deepFreeze(cloneFixed(record)) : null;
}
export function assessFixedResearchRecord(record) {
if (!isKnownFixedRecord(record)) {
return deepFreeze({ accepted: false, status: 'stop-unknown-fixed-record', reasons: ['record-is-not-a-known-fixed-literal'], nextAction: 'use-a-named-fixture-record', productionEffect: 'not-attempted' });
}
const reasons = [];
if (!record.source.version || !record.source.pin) reasons.push('missing-version-or-immutable-pin');
if (!record.scope) reasons.push('missing-scope');
if (!record.observation.reproducible || !record.observation.artifact) reasons.push('missing-reproducible-observation');
if (record.claimClass === 'marketing-promise') reasons.push('marketing-copy-is-not-evidence');
if (record.conclusion === 'truth-not-established') reasons.push('integrity-does-not-establish-semantic-truth');
if (record.conclusion === 'compatibility-not-established') reasons.push('compatibility-needs-versioned-primary-source');
const accepted = reasons.length === 0;
const status = accepted ? 'evidence-ready-for-synthetic-hand-off' : (reasons.includes('missing-version-or-immutable-pin') ? 'repair-source-pin' : 'hold-claim');
const nextAction = accepted ? 'hand-off-with-scope' : (reasons.includes('marketing-copy-is-not-evidence') ? 'replace-promise-with-observable-claim' : 'collect-a-versioned-primary-observation');
return deepFreeze({ accepted, status, reasons, nextAction, claimId: record.id, productionEffect: 'not-attempted' });
}
export function createFixedResearchLog(record) {
const assessment = assessFixedResearchRecord(record);
if (!assessment.claimId) return assessment;
return deepFreeze({
logVersion: 'fixed-research-log-v1',
claimId: assessment.claimId,
statement: record.statement,
sourcePin: record.source.pin || 'missing',
sourceVersion: record.source.version || 'missing',
locator: record.source.locator,
observedArtifact: record.observation.artifact,
scope: record.scope,
status: assessment.status,
nextAction: assessment.nextAction,
boundary: 'fixed synthetic in-memory record; no network, files, people, systems, telemetry, Git, CI or production effect',
});
}
export function createFixedResearchLogById(id) {
return createFixedResearchLog(createFixedResearchRecord(id));
}
export function compareFixedResearchRecords(leftId, rightId) {
const left = createFixedResearchRecord(leftId);
const right = createFixedResearchRecord(rightId);
const leftAssessment = assessFixedResearchRecord(left);
const rightAssessment = assessFixedResearchRecord(right);
return deepFreeze({
sameSourcePin: Boolean(left?.source.pin) && left.source.pin === right?.source.pin,
leftStatus: leftAssessment.status,
rightStatus: rightAssessment.status,
decision: leftAssessment.accepted && rightAssessment.accepted ? 'compare-scoped-observations' : 'stop-before-comparison',
productionEffect: 'not-attempted',
});
}
export function runFixedResearchFixture() {
const pinned = createFixedResearchRecord('pinned-representation-v1');
const versionless = createFixedResearchRecord('versionless-compatibility-v1');
const marketing = createFixedResearchRecord('marketing-outcome-v1');
const signature = createFixedResearchRecord('signature-overreach-v1');
const pinnedReport = assessFixedResearchRecord(pinned);
const versionlessReport = assessFixedResearchRecord(versionless);
const marketingReport = assessFixedResearchRecord(marketing);
const signatureReport = assessFixedResearchRecord(signature);
const log = createFixedResearchLog(pinned);
const comparison = compareFixedResearchRecords('pinned-representation-v1', 'versionless-compatibility-v1');
return deepFreeze({ assertions: deepFreeze({
pinnedAccepted: pinnedReport.accepted,
pinnedHasScopedHandOff: pinnedReport.nextAction === 'hand-off-with-scope',
versionlessStopsForPin: versionlessReport.reasons.includes('missing-version-or-immutable-pin'),
versionlessNeedsPrimarySource: versionlessReport.nextAction === 'collect-a-versioned-primary-observation',
marketingStopsForPromise: marketingReport.reasons.includes('marketing-copy-is-not-evidence'),
signatureDoesNotProveTruth: signatureReport.reasons.includes('integrity-does-not-establish-semantic-truth'),
logKeepsArtifact: log.observedArtifact === 'ETag: "fixed-v4"',
logKeepsBoundary: log.boundary.includes('no network'),
comparisonStops: comparison.decision === 'stop-before-comparison',
unknownRecordStops: assessFixedResearchRecord({}).status === 'stop-unknown-fixed-record',
frozenFixture: Object.isFrozen(FIXED_RESEARCH_RECORDS),
}) });
}
function revision(meta, parts, sources) {
const contentHtml = parts.join('\n') + '\n' + h2('Проверяемые источники') + sourceList(sources);
const proseLength = bodyText(contentHtml).length;
if (proseLength < 5000 || proseLength > 15000) throw new Error(meta.slug + ': body length ' + proseLength);
return deepFreeze({ ...meta, contentHtml, proseLength });
}
const practice = revision({
slug: 'editorial-2025-11-practice-research-method',
title: 'Журнал утверждений: как проверить источник до того, как ссылка станет решением',
categories: ['Исследование', 'Инженерная практика'],
cover: '/assets/editorial/2025/research-method-2025-evidence-ladder.svg',
excerpt: 'Практический метод для статьи и технического решения: разложить утверждение, версию, наблюдение и следующий шаг по журналу, не выдавая ссылку за доказательство.',
readingMinutes: 13,
}, [
p('Проблема возникает тихо: в статье уже есть пять ссылок, но ни одна не отвечает, какая версия прочитана, что именно в ней наблюдалось и какой вывод из этого допустим. Цена такой экономии — решение, привязанное к изменившейся странице, спор о пересказе и повторная проверка прямо перед выпуском, когда контекст уже потерян.'),
p('Собирать закладки недостаточно. Нужен журнал утверждений: маленькая карточка, где отдельны фраза, область действия, первоисточник, версия или неизменяемый pin, точное наблюдение и статус. Проверка проста: другой инженер открывает карточку и видит, что подтверждено буквально, а что ещё надо исследовать. Действие — начинать не с поиска «лучшей ссылки», а с одного проверяемого предложения.'),
h2('Что именно считаем утверждением'),
p('Утверждение — не тема и не URL. «HTTP умеет валидаторы» слишком широко: из него нельзя понять, о какой операции или представлении идёт речь. Рабочая формулировка уже содержит объект и границу: «в fixed source-card поле ETag относится к выбранному представлению fixed-v4». Она скромна, зато её можно сопоставить с источником, тестом или фрагментом документа. Если фразу нельзя опровергнуть изменением одного поля, это обычно не утверждение, а лозунг.'),
p('В реальной статье объектом будет релиз, API-ответ, PDF или спецификация. В этом пакете объектов извне нет: все карточки заранее заданы JavaScript literals в памяти. Это сделано намеренно. Метод проверяет форму исследовательской записи, а не имитирует доступ к сети, Git, CI, людям, данным продукта или телеметрии. Положительный результат означает лишь готовность synthetic hand-off, а не правильность чьего-либо production-решения.'),
figure('/assets/editorial/2025/research-method-2025-evidence-ladder.svg', 'Лестница доказательств: от рекламной фразы к версии, точному наблюдению, ограниченному утверждению и явному следующему действию; красная стрелка показывает остановку для неприкреплённого источника.', 'Лестница не измеряет авторитет сайта. Она показывает, какие сведения необходимы, чтобы ссылка стала ограниченным основанием для следующего шага.'),
table('Поля минимального журнала', ['Поле', 'Вопрос', 'Пример fixed fixture', 'Ошибка без поля'], [
['statement', 'что именно сказано?', 'validator относится к fixed-v4', 'пересказ вместо факта'],
['scope', 'где это верно?', 'одна synthetic card', 'вывод расширяют на систему'],
['source pin', 'какую версию открывали?', 'rfc-9110-june-2022', 'страница могла измениться'],
['locator', 'где лежит факт?', '§8.8.3', 'нельзя быстро перепроверить'],
['observation', 'что увидели буквально?', 'ETag: "fixed-v4"', 'ссылка подменяет наблюдение'],
['status', 'что разрешено дальше?', 'hand-off-with-scope', 'неизвестное проходит как факт'],
]),
h2('Сначала зафиксировать источник, потом читать вывод'),
p('Версия — это не украшение в сноске. Она связывает вывод с конкретным представлением документа: датированным RFC, tagged release, опубликованным PDF или архивной копией до даты статьи. URL без версии остаётся маршрутом, но не идентификатором прочитанного текста. Даже HTTP-валидатор не спасает от этой путаницы: RFC 9110 определяет ETag как непрозрачный validator выбранного представления. Он помогает различать представления, но не сообщает читателю, какая продуктовая семантика обещана между версиями.'),
p('Поэтому в журнале полезно хранить два разных поля. <code>sourcePin</code> отвечает на вопрос «какую публикацию можно снова открыть». <code>observedArtifact</code> отвечает на вопрос «что именно из неё использовано». В примере pin — имя датированного RFC, а artifact — fixed literal <code>ETag: "fixed-v4"</code>. Если заменить одно другим, появится ложная точность: hash или дата выглядят солидно, но не объясняют, почему именно этот фрагмент поддерживает фразу автора.'),
h2('Исполняемая карточка вместо ручной уверенности'),
code("import { createFixedResearchRecord, assessFixedResearchRecord, createFixedResearchLog } from './upgrade-2025-11.mjs';\n\nconst record = createFixedResearchRecord('pinned-representation-v1');\nconst report = assessFixedResearchRecord(record);\nconst log = createFixedResearchLog(record);\nconsole.log({ status: report.status, pin: log.sourcePin, artifact: log.observedArtifact });\n// { status: 'evidence-ready-for-synthetic-hand-off', pin: 'rfc-9110-june-2022', artifact: 'ETag: \"fixed-v4\"' }") ,
p('Этот фрагмент импортирует только публичные exports модуля. Он не делает запрос и не использует часы; вся проверка — сравнение заранее заданной карточки с набором правил. Принятый статус не говорит, что «источник истинный». Он говорит намного уже: у одной synthetic записи есть pin, область, наблюдаемый artifact и отсутствие известных в fixture причин остановить hand-off. Такая узость полезна: следующий редактор знает, что ему не нужно заново угадывать основу фразы.'),
h2('Отделить маркетинговое обещание от исследовательского шага'),
p('Фраза «метод делает каждое утверждение надёжным» может находиться на красивой странице и быть добросовестным описанием намерения. Для журнала это всё равно marketing promise. В ней нет объекта проверки, метода, контекста и условия неуспеха. Рядом с ней можно записать только наблюдение о тексте: «в fixed copy r1 есть слова trust every claim». Нельзя переписать её как результат измерения или как гарантию качества будущего исследования.'),
p('Это различие не требует цинизма к источнику. Оно сохраняет полезную роль обещания: маркетинговая формулировка может подсказать гипотезу или термин для поиска. Но status должен остановить её до тех пор, пока не появится первичный материал с версией, локатором и наблюдаемым фактом. Такой стоп дешевле поздней переписки «мы же дали ссылку»: команда видит, что спорит не о доверии к бренду, а о недостающем типе evidence.'),
h2('Шесть шагов перед публикацией'),
ol([
'<strong>Выписать одно предложение.</strong> Убрать слова «лучше», «быстрее» и «надёжно», пока не появится объект и условие.',
'<strong>Назвать границу.</strong> Зафиксировать версию, релиз, PDF, commit или архивную копию, которая существовала к дате статьи.',
'<strong>Записать locator.</strong> Оставить section, таблицу, endpoint или номер примера, а не только корневой URL.',
'<strong>Сохранить наблюдение.</strong> Пересказать или процитировать минимальный факт своими словами без расширения результата.',
'<strong>Поставить status.</strong> Разрешить hand-off, запросить первичный материал либо остановить marketing promise.',
'<strong>Назвать следующий шаг.</strong> Указать, что проверит другой человек, а не скрывать неизвестное в общем выводе.',
]),
h2('Как журнал переживает изменение документа'),
p('Страница может поменять структуру, автора и даже вывод без редиректа. Журнал не обещает предотвратить это изменение. Он делает устаревание видимым: у записи есть именно тот pin, на который опирался текст. При пересмотре новой версии не нужно сканировать весь материал. Достаточно взять строку claimId, открыть закреплённый источник, сравнить locator и решить: подтвердить прежнее утверждение, сузить scope или вернуть статус в hold.'),
p('Важен порядок действий. Не заменяйте старый pin новым молча. Сначала сохраните, что старая карточка подтверждала на своей временной границе; затем заведите новую карточку для нового представления. Иначе история исследования превращается в один current URL, который не объясняет, почему прошлый материал был написан именно так. Для автора M8 это не архивная педантичность, а способ дешево локализовать влияние обновления.'),
table('Статус и следующий ход', ['Статус', 'Что уже есть', 'Чего нет', 'Следующее действие'], [
['evidence-ready-for-synthetic-hand-off', 'pin, scope, observation', 'обобщение на production', 'передать с границей'],
['repair-source-pin', 'текст и ссылка', 'версия или immutable reference', 'найти первичную публикацию'],
['hold-claim', 'часть контекста', 'достаточная связь факта и вывода', 'сузить statement'],
['stop-unknown-fixed-record', 'произвольный объект', 'проверяемый fixture', 'вернуться к named record'],
]),
h2('Ограничение метода и следующий эксперимент'),
p('Журнал не заменяет рецензию, юридическую проверку, предметную экспертизу или эксперимент. Он не вычисляет доверие к издателю и не превращает одну техническую спецификацию в доказательство работы конкретного продукта. Его действие гораздо уже: не дать версии, наблюдению, claim и обещанию слиться в одно поле «ссылка». Если для фразы нужно измерение, в карточке должно появиться отдельное описание метода и данных; в этом synthetic пакете таких данных нет.'),
p('Начните с трёх самых дорогих утверждений в ближайшей статье: тех, от которых зависит выбор API, ограничение безопасности или причина изменения архитектуры. Для каждого сделайте одну строку журнала. Если строка не помещается в короткую форму, вопрос пока слишком широкий. Сначала сузьте формулировку, затем ищите дополнительный источник. Так публикация получает не больше ссылок, а меньше неявных переходов от прочитанного текста к решению.'),
], [
{ key: 'prov', use: 'PROV-DM даёт словарь entity и activity; здесь он помогает раздельно назвать источник и действие его проверки.', boundary: 'Модель происхождения не подтверждает истинность технического claim сама по себе.' },
{ key: 'http', use: 'RFC фиксирует ETag как opaque validator выбранного representation; это опора для различения представления и смыслового вывода.', boundary: 'HTTP validator не равен версии продукта и не устанавливает факт вне полученного representation.' },
{ key: 'vc', use: 'Recommendation прямо разделяет криптографическую verification и оценку truth of claims.', boundary: 'Документ не задаёт готовый research log для веб-страниц или решение о публикации.' },
]);
const mechanism = revision({
slug: 'editorial-2025-11-mechanism-research-method',
title: 'Версия, представление, утверждение: механизм проверки источника без ложной уверенности',
categories: ['Архитектура', 'Исследование'],
cover: '/assets/editorial/2025/research-method-2025-claim-status-matrix.svg',
excerpt: 'Модель проверки источника: распознать разные уровни evidence, не принять validator или подпись за смысловой факт и остановить вывод до подходящего первоисточника.',
readingMinutes: 13,
}, [
p('Проблема механики источников появляется, когда один признак пытаются использовать как ответ на все вопросы. У страницы есть дата — значит, она верна; у файла есть подпись — значит, верно каждое предложение; есть ETag — значит, найдена нужная версия. Цена подмены — архитектурное решение с неявной предпосылкой, которое невозможно объяснить после смены документа или инцидента.'),
p('Надёжнее разложить проверку на четыре уровня: публикация, её конкретное представление, наблюдаемое утверждение и решение, которое из него следует. Между уровнями нет автоматического перехода. Проверка — для каждого слова в выводе спросить, кто его несёт: pin, artifact, первичный текст или отдельный эксперимент. Действие — выдавать status только на тот уровень, который действительно наблюдался.'),
h2('Четыре объекта вместо одного слова «источник»'),
p('Публикация — например, RFC или датированная рекомендация. Представление — конкретная HTML-страница, PDF или response, которые читатель может открыть. Утверждение — короткая фраза о поведении или факте. Решение — действие команды: изменить интеграцию, добавить проверку, запретить перенос. Все четыре объекта могут иметь похожие имена, но отвечают на разные вопросы. Смешение начинается, когда URL начинают называть доказательством решения.'),
p('W3C PROV-DM полезен не как готовый workflow, а как минимальный язык связей. Он определяет entity как вещь с фиксированными аспектами, а activity как действие, которое использует или создаёт entities. В журнале pinned document — entity, чтение section — activity, а statement — отдельный артефакт с scope. Этого уже достаточно, чтобы не выдавать акт чтения за свойство самой системы, о которой читали.'),
figure('/assets/editorial/2025/research-method-2025-claim-status-matrix.svg', 'Матрица статусов утверждения: строки показывают наличие версии, наблюдаемого фрагмента и связь с выводом; зелёная клетка допускает ограниченный hand-off, жёлтые требуют ремонта, красные останавливают promise.', 'Матрица показывает не рейтинг источников, а выбор следующего действия при разном составе evidence.'),
table('Сигнал, который наблюдаем, и граница вывода', ['Сигнал', 'Что можно сказать', 'Что не следует заключать', 'Нужный дополнительный факт'], [
['датированный release', 'известна временная граница публикации', 'все её тезисы применимы к продукту', 'точный section и scope'],
['ETag или Last-Modified', 'сервер различает representation для protocol validation', 'это номер продуктовой версии', 'версионная политика издателя'],
['валидная подпись', 'проверена authorship/целостность защищённого объекта', 'семантический claim истинный', 'прямой первичный факт'],
['landing headline', 'так сформулировано обещание', 'результат воспроизводим', 'метод и наблюдение'],
]),
h2('Почему validator — это не смысловая версия'),
p('RFC 9110 называет ETag непрозрачным validator выбранного representation. Его значение специально не обязано раскрывать внутреннее устройство документа; клиенту достаточно сравнить строки в нужном протокольном контексте. Это хорошо для conditional request и cache validation. Но из opaque tag нельзя вывести, что он соответствует release number, change log или смыслу отдельного абзаца. У одного сервера tag может быть hash содержимого, у другого — внутренний счётчик, у третьего — комбинация вариантов представления.'),
p('Last-Modified тоже не следует превращать в доказательство исторической версии. RFC описывает его как время изменения представления, обычно зависящее от многих частей за интерфейсом ресурса, и отдельно допускает слабое сравнение. Даже идеальное время ответа отвечает лишь на вопрос о том, что прислал server. Для исторической статьи нужен более сильный anchor: опубликованный PDF, tagged release, commit или archive capture до даты публикации. Validator можно сохранить как дополнительное наблюдение, но не как замену pin.'),
h2('Подпись подтверждает автора, а не тезис'),
p('У криптографической проверки есть чёткая полезная граница. W3C VC Data Model определяет verification как проверку того, что credential является authentic and current statement issuer или presenter, и прямо говорит: verification credential не означает evaluation truth of encoded claims. Это редкая формулировка, которую стоит переносить почти дословно в инженерную дисциплину: целостность и авторство representation важны, но не закрывают вопрос, верна ли его интерпретация в другой системе.'),
p('Практический риск здесь не теоретический. Подписанный документ может честно описывать ограниченную конфигурацию, а автор статьи незаметно выберет другое условие. Подпись не проверит, тот ли endpoint, лимит, дата или право доступа использованы в выводе. Поэтому в механической модели source integrity и claim evidence — разные колонки. Первая защищает цепочку передачи документа; вторая требует прямой связи между locator, наблюдением и фразой в тексте.'),
h2('Исполняемый отрицательный пример'),
code("import { createFixedResearchRecord, assessFixedResearchRecord } from './upgrade-2025-11.mjs';\n\nconst signedClaim = createFixedResearchRecord('signature-overreach-v1');\nconst report = assessFixedResearchRecord(signedClaim);\nconsole.log({ status: report.status, reasons: report.reasons });\n// { status: 'hold-claim', reasons: ['integrity-does-not-establish-semantic-truth'] }") ,
p('Функция не проверяет настоящую подпись. Она сравнивает fixed literal с правилом fixture: в карточке наблюдается только <code>signature: valid</code>, а statement делает более сильный семантический вывод. Поэтому status остаётся hold. Такой пример полезнее правдоподобной криптографии в учебном коде: нет ложного впечатления, что библиотека или ключ уже решили исследовательскую часть задачи.'),
h2('Механика статуса: не рейтинг, а управление неизвестным'),
p('Статус не должен отражать настроение автора словами «надёжный» или «сомнительный». Он выбирает следующий разрешённый переход. <code>evidence-ready-for-synthetic-hand-off</code> появляется только у record с известным pin, scope и воспроизводимым artifact. <code>repair-source-pin</code> говорит, что фраза может быть разумной, но её источник нельзя зафиксировать исторически. <code>hold-claim</code> оставляет работу незавершённой, потому что наблюдение не несёт заявленного вывода.'),
p('Это различие уменьшает стоимость ревью. Вместо спора «достаточно ли верить странице» reviewer видит конкретную недостающую связь. Для versionless card нужно найти versioned primary source. Для marketing copy — заменить promise наблюдаемой формулировкой или получить измерение. Для signature overreach — сузить statement до authorship либо добавить независимый evidence факта. Ни один status не является наказанием; это маршрут, который экономит следующий поиск.'),
table('Переходы между уровнями', ['Откуда', 'Куда', 'Проверка перехода', 'Стоп-сигнал'], [
['publication', 'representation', 'есть immutable pin или дата capture', 'только current URL'],
['representation', 'observation', 'есть locator и точный artifact', 'пересказ без фрагмента'],
['observation', 'claim', 'scope не шире факта', 'обещание результата'],
['claim', 'decision', 'добавлены условия применения', 'перенос на неизвестный контекст'],
]),
h2('Проверка сравнения до того, как появляются метрики'),
p('Сравнение двух источников начинается не с подсчёта ссылок, а с совместимости объектов. Сначала спросите: это две версии одного вида документа или разные классы evidence? Совпадают ли scope, термин и наблюдаемый объект? Сравнение response header с рекламным обещанием может быть полезно как контраст, но не как подтверждение. Если хотя бы одна карточка не прошла status, агрегирование результатов лишь делает неопределённость более аккуратно оформленной.'),
code("import { compareFixedResearchRecords } from './upgrade-2025-11.mjs';\n\nconst result = compareFixedResearchRecords(\n 'pinned-representation-v1',\n 'versionless-compatibility-v1',\n);\nconsole.log(result.decision);\n// 'stop-before-comparison' — second record has no source pin") ,
p('Здесь нет числа «качества источника». Сравнение останавливается потому, что один record нельзя повторно адресовать к версии. Это правильный отказ: он сохраняет источник неопределённости, а не маскирует его средним баллом. Если требуется оценка вариантов, после pin и observation нужно отдельно описать критерий решения. В текущем fixture такого критерия нет, значит никакая рекомендация продукта не выдаётся.'),
h2('Пять диагностических вопросов к сильному выводу'),
ol([
'<strong>Назвать уровень.</strong> Это publication, representation, observation, claim или уже decision?',
'<strong>Проверить адресуемость.</strong> Можно ли открыть именно тот release, PDF, commit или capture, который использован?',
'<strong>Проверить literal.</strong> Есть ли locator и artifact, а не только удобный пересказ section?',
'<strong>Сравнить силу.</strong> Не добавляет ли statement слово, которого нет в наблюдении: «всегда», «безопасно», «поддерживает»?',
'<strong>Выбрать stop.</strong> Если связь не доказана, вернуть hold или repair вместо технической рекомендации.',
]),
h2('Граница механизма и следующий шаг'),
p('Модель не делает веб неизменяемым и не обнаруживает ложь автоматически. Она также не заменяет эксперта предметной области: primary source может корректно описывать протокол, но не содержать нужный operational факт. Её сила в дисциплине разделения. Каждый раз, когда вывод звучит сильнее, чем наблюдение, status даёт место для явного stop. В условиях технического блога это честнее и быстрее, чем заполнять пробел авторитетной интонацией.'),
p('Возьмите одну ссылку из текущего черновика и подпишите четыре объекта: publication, representation, claim, decision. Затем удалите любой один из них и посмотрите, что перестанет быть проверяемым. Часто исчезает не URL, а связь между строкой источника и практическим советом. Её и нужно восстановить через source pin, locator, scope или отдельный эксперимент. После этого текст может стать короче, но у него появится механизм, который читатель способен воспроизвести.'),
], [
{ key: 'http', use: 'RFC 9110 определяет ETag и Last-Modified как validator fields для selected representation и описывает их сильные и слабые свойства.', boundary: 'Документ не назначает ETag релизной версией и не интерпретирует смысл содержимого за автора статьи.' },
{ key: 'vc', use: 'VC Data Model фиксирует границу: криптографическая verification не оценивает truth encoded claims.', boundary: 'Recommendation не доказывает правильность конкретной интеграции и не заменяет independent evidence.' },
{ key: 'prov', use: 'PROV-DM различает entity и activity; это основание для раздельных полей publication, representation и reading activity.', boundary: 'Стандарт не задаёт шкалу доверия или автоматический verdict исследовательскому выводу.' },
]);
const field = revision({
slug: 'editorial-2025-11-field-research-method',
title: 'Полевой разбор ссылок: как вести журнал исследования для технического решения',
categories: ['Практика', 'Техническое письмо'],
cover: '/assets/editorial/2025/research-method-2025-research-log-loop.svg',
excerpt: 'Полевой цикл проверки источников: принять вопрос, зафиксировать claim и первоисточник, остановить лишний вывод, передать ограниченный результат и обновить журнал после нового факта.',
readingMinutes: 13,
}, [
p('Проблема проявляется на hand-off: автор оставляет список ссылок и фразу «исследование подтверждает», а следующий инженер не понимает, что проверено и на какую дату. Цена — повторный поиск, решения на основе чужого пересказа и риск, что при обновлении документа команда исправит уже не ту причину. Это не лечится длинной библиографией, если в ней нет статуса конкретного claim.'),
p('Полевой метод начинается с вопроса решения, а не с обзора рынка. Сначала формулируем, что требуется выбрать или запретить; затем создаём claim card, ищем первоисточник, фиксируем version и locator, записываем наблюдение и передаём только ограниченный вывод. Проверка hand-off: получатель должен суметь назвать следующий допустимый шаг без догадки о намерении автора. Действие — делать журнал частью черновика, а не посмертным приложением.'),
h2('Один вопрос, одна очередь неизвестных'),
p('Представим учебный запрос: «можно ли перенести synthetic compatibility statement в документацию?» Ответа «да» или «нет» здесь ещё нет. Сначала журнал хранит вопрос, а не вывод. Затем появляются два возможных record: pinned representation с названной версией и versionless note без неё. Они не равны только потому, что оба содержат слово support. У первого можно повторить чтение, у второго пока не ясно, какую публикацию вообще следует открыть.'),
p('Такое поле не требует собирать реальные обращения, интервью, файлы или production telemetry. Весь пример ниже состоит из fixed objects в памяти. Он не пытается исследовать живую систему и не создаёт задачу во внешнем трекере. Его функция — показать порядок журналирования: неизвестное остаётся visible until repaired. Это особенно полезно в редактуре, где соблазн написать гладкий вывод сильнее, чем признать отсутствие version pin.'),
figure('/assets/editorial/2025/research-method-2025-research-log-loop.svg', 'Петля исследовательского журнала: вопрос превращается в claim card, затем в pinned observation, статус, ограниченный hand-off и возвращается к вопросу при новом факте; красная ветка останавливает marketing promise.', 'Петля не производит решение сама. Она сохраняет origin каждого вывода и делает причину возврата наблюдаемой.'),
table('Полевая карточка для hand-off', ['Шаг', 'Артефакт', 'Разрешённый результат', 'Что останавливаем'], [
['вопрос', 'decision question', 'список неизвестных', 'готовую рекомендацию'],
['источник', 'version + locator', 'повторное открытие representation', 'current URL без pin'],
['наблюдение', 'короткий artifact', 'узкий claim', 'пересказ всего документа'],
['статус', 'next action', 'hand-off или repair', 'молчаливое допущение'],
['обновление', 'новая card', 'видимая смена основания', 'перезапись старого evidence'],
]),
h2('Как проходит один inspection'),
p('Inspection — это не «прочитать всё». Это конечная activity с входом и выходом. Вход: claim card с вопросом и границей времени. Действие: открыть закреплённый первичный материал и найти один locator. Выход: observed artifact и status. PROV-DM даёт здесь полезную рамку: document и extracted note — entities, а inspection связывает их без притворства, будто note сам появился вместе с фактом. Если источник не отвечает на нужную фразу, результат inspection — не плохая ссылка, а статус hold.'),
p('Ниже фиксированный example показывает счастливую, но ограниченную ветку. Карточка хранит только одно наблюдение о validator field у synthetic representation. У неё нет метрики влияния, совместимости настоящего SDK или прогноза поведения сервиса. Поэтому hand-off из функции заканчивается словами <code>with-scope</code>. В поле это означает: следующий участник может использовать запись как адресуемое основание для узкого вопроса, но обязан отдельно подтвердить любой более широкий вывод.'),
h2('Исполняемый hand-off без внешнего следа'),
code("import { createFixedResearchLogById } from './upgrade-2025-11.mjs';\n\nconst handoff = createFixedResearchLogById('pinned-representation-v1');\nconsole.log({ claim: handoff.claimId, status: handoff.status, next: handoff.nextAction });\n// { claim: 'pinned-representation-v1', status: 'evidence-ready-for-synthetic-hand-off', next: 'hand-off-with-scope' }") ,
p('Все строки результата выводятся из literals, определённых в том же module. Это важно для воспроизводимости: другой запуск не зависит от текущего времени, прав на сервис, содержимого диска или изменения чужой страницы. В то же время именно поэтому результат нельзя выдать за исследование реального продукта. Fixture проверяет правила журнала: source pin заполнен, artifact конкретен, scope не пуст и нет правила, разрешающего promise как evidence.'),
h2('Как вернуть недостающий факт, а не переписать вывод'),
p('Versionless card часто вызывает неверную реакцию: автор начинает дополнять текст догадками о том, какая версия имелась в виду. В журнале repair выглядит скучнее и полезнее. Статус <code>repair-source-pin</code> говорит: оставь claim в очереди, найди immutable primary publication, зафиксируй version, затем прочитай locator заново. Нельзя лечить пустой pin добавлением цитат вокруг current page. Новые цитаты увеличат видимость работы, но не сделают прошлое чтение воспроизводимым.'),
p('Marketing promise требует другого ремонта. У него может быть аккуратная дата и даже подписанный файл, однако в нём отсутствует измеряемый предмет. Следующий ход — либо переписать строку как наблюдение текста, либо собрать первичный метод: population, conditions, observed outcome и ограничения. Если этих данных нет, journal оставляет promise в hold. Автор не обязан спорить с обещанием; ему достаточно не использовать его как опору для технического решения.'),
h2('Короткая процедура редактора'),
ol([
'<strong>Найти decision sentence.</strong> Подчеркнуть фразу, которая меняет рекомендацию, а не весь обзор.',
'<strong>Создать claim card.</strong> Отдельно записать statement, scope и дату, к которой материал должен существовать.',
'<strong>Выбрать первоисточник.</strong> Предпочесть release, commit, published PDF или dated archive capture изменяемой странице.',
'<strong>Выписать один artifact.</strong> Сохранить locator и точное наблюдение, достаточное для данной фразы.',
'<strong>Поставить маршрут.</strong> Hand-off допустим только с known pin; иначе repair или hold.',
'<strong>Не прятать возврат.</strong> При новом evidence создать новую строку и показать, что прежний вывод был ограничен другим representation.',
]),
h2('Проверка всей пачки, а не одной красивой ссылки'),
p('Полезное ревью проходит по обратному маршруту: от самой сильной рекомендации к карточке, затем к artifact и закреплённой публикации. Если по дороге исчезает scope, значит claim вырос в процессе письма. Если есть source pin, но нет locator, reader вынужден заново интерпретировать документ. Если есть locator, но observation заменён фразой «всё поддерживается», непонятно, какие условия скрыты. Каждая такая дыра превращается в конкретный repair, а не в эмоциональную оценку качества исследования.'),
p('Для нескольких claims не нужен общий балл достоверности. Он часто скрывает, что один важный вывод не имеет первоисточника. Лучше построить очередь по стоимости ошибки: сначала statements, которые меняют API contract, безопасность или необратимую миграцию; затем поясняющие детали. У каждой строки свой status. Так редактор может передать опубликованную часть с границами и не блокировать её из-за второстепенного вопроса, который ещё в repair.'),
table('Решение на hand-off', ['Состояние карточки', 'Что получает следующий человек', 'Что он не должен делать', 'Безопасный ход'], [
['есть version, locator, artifact и scope', 'ограниченный claim', 'распространять на чужой context', 'проверить условие применения'],
['нет version pin', 'вопрос на поиск источника', 'цитировать current page как историю', 'найти dated primary publication'],
['есть marketing phrase', 'наблюдение текста', 'объявлять outcome доказанным', 'запросить метод или сузить текст'],
['появился новый source', 'новую card рядом со старой', 'переписать прошлое чтение', 'сравнить scope и decision'],
]),
h2('Обновление журнала без потери времени'),
p('Когда появляется новая версия документа, не надо немедленно менять старую карточку. Сначала это отдельная observation activity: новая publication date, другой representation, новый locator и новый artifact. После сравнения можно написать, что decision не изменился, что scope сузился или что старый claim больше не годится. Такой журнал полезен в incident review и в подготовке статьи одинаково: он отвечает на вопрос, откуда взялся вывод на конкретную дату, не требуя помнить весь путь в голове.'),
p('Внутри этого draft-пакета fixture проверяет именно такие стопы. Он отклоняет произвольный input, останавливает versionless record, не разрешает marketing promise и не считает valid synthetic signature доказательством truth. Проверка не выходит за память процесса. Если после неё нужно реальное действие, это уже новый work item с собственным источником данных, правами и review. Граница не ослабляет текст: она делает следующий ответственный шаг видимым.'),
h2('Граница поля и первый шаг завтра'),
p('Полевой журнал не заменяет доверенную архивную систему, редакционный процесс или оценку рисков. Он не даёт автоматического критерия, сколько первоисточников достаточно, и не позволяет решить, что один официальный PDF применим к любой архитектуре. Его нельзя использовать как способ собирать данные о людях или скрытно оценивать работу команды. В примере нет таких данных и нет side effects; это сознательное ограничение учебного механизма.'),
p('Завтра выберите одну рекомендацию, от которой зависит реальное изменение, и проведите её по петле без спешки. Получится либо короткий hand-off с pin, locator и scope, либо честная очередь repair. Оба результата лучше списка ссылок: первый можно повторить, второй показывает стоимость неизвестного до того, как оно попадёт в опубликованный совет. Через несколько таких проходов журнал становится не бюрократией, а памятью технического решения.'),
], [
{ key: 'prov', use: 'PROV-DM используется как vocabulary для inspection activity и связанных с ней document/note entities в журнале.', boundary: 'Recommendation не даёт evidence quality score и не предписывает порядок редакционного решения.' },
{ key: 'http', use: 'RFC 9110 позволяет точно назвать validator как свойство selected representation, а не как универсальную версию публикации.', boundary: 'Стандарт HTTP не является источником compatibility или product behaviour вне конкретного protocol statement.' },
{ key: 'vc', use: 'W3C document фиксирует, что cryptographic verification не означает проверку truth закодированного claim; этим ограничен разбор signed evidence.', boundary: 'Никакая VC-схема не запускается в fixture и не проверяет реальные credentials.' },
]);
export const revisions = deepFreeze([practice, mechanism, field].map(({ proseLength, ...item }) => item));
if (process.argv.includes('--verify-fixture')) {
const assertions = runFixedResearchFixture().assertions;
const failed = Object.entries(assertions).filter(([, value]) => value !== true).map(([key]) => key);
if (failed.length) {
process.stderr.write('FAIL fixture: ' + failed.join(', ') + '\n');
process.exitCode = 1;
} else {
process.stdout.write('PASS fixture: ' + Object.keys(assertions).length + '/' + Object.keys(assertions).length + ' assertions\n');
}
}
if (process.argv.includes('--print-revisions')) process.stdout.write(JSON.stringify(revisions) + '\n');