Files
progcode/web/scripts/upgrade-2025-11.mjs
huncode def4f0a1f5
Build and deploy / deploy (push) Successful in 16s
revise November 2025 source verification articles
2026-07-31 18:34:41 +03:00

362 lines
62 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
function escapeHtml(value) { return String(value).replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#039;'); }
const p = (text) => '<p>' + text + '</p>';
const h2 = (text) => '<h2>' + text + '</h2>';
const code = (text) => '<pre><code>' + escapeHtml(text) + '</code></pre>';
const ol = (items) => '<ol>' + items.map((item) => '<li>' + item + '</li>').join('') + '</ol>';
const figure = (src, alt, caption) => '<figure><img src="' + src + '" alt="' + 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');