This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
function escapeHtml(value) { return String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); }
|
||||
const p = (text) => '<p>' + text + '</p>';
|
||||
const h2 = (text) => '<h2>' + text + '</h2>';
|
||||
const code = (text) => '<pre><code>' + escapeHtml(text) + '</code></pre>';
|
||||
const ol = (items) => '<ol>' + items.map((item) => '<li>' + item + '</li>').join('') + '</ol>';
|
||||
const figure = (src, alt, caption) => '<figure><img src="' + src + '" alt="' + alt + '" loading="lazy" /><figcaption>' + caption + '</figcaption></figure>';
|
||||
const table = (caption, headers, rows) => '<div class="table-scroll"><table><caption>' + caption + '</caption><thead><tr>' + headers.map((cell) => '<th scope="col">' + cell + '</th>').join('') + '</tr></thead><tbody>' + rows.map((row) => '<tr>' + row.map((cell) => '<td>' + cell + '</td>').join('') + '</tr>').join('') + '</tbody></table></div>';
|
||||
|
||||
function cloneFixed(value) { return JSON.parse(JSON.stringify(value)); }
|
||||
function deepFreeze(value) { if (value && typeof value === 'object' && !Object.isFrozen(value)) { Object.values(value).forEach(deepFreeze); Object.freeze(value); } return value; }
|
||||
function plainText(html) { return html.replace(/<[^>]+>/g, ' ').replace(/&(?:quot|amp|lt|gt|#039);/g, ' ').replace(/\s+/g, ' ').trim(); }
|
||||
function bodyText(html) { return plainText(html.replace(/<h2>Проверяемые источники<\/h2>[\s\S]*?(?=<h2>|$)/, '')); }
|
||||
|
||||
const REFERENCES = deepFreeze({
|
||||
rfc9110: { title: 'RFC 9110 — HTTP Semantics', url: 'https://www.rfc-editor.org/rfc/rfc9110', version: 'RFC 9110, June 2022, DOI 10.17487/RFC9110' },
|
||||
rfc9112: { title: 'RFC 9112 — HTTP/1.1', url: 'https://www.rfc-editor.org/rfc/rfc9112', version: 'RFC 9112, June 2022, DOI 10.17487/RFC9112' },
|
||||
rfc9114: { title: 'RFC 9114 — HTTP/3', url: 'https://www.rfc-editor.org/rfc/rfc9114', version: 'RFC 9114, June 2022, DOI 10.17487/RFC9114' },
|
||||
rfc8446: { title: 'RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3', url: 'https://www.rfc-editor.org/rfc/rfc8446', version: 'RFC 8446, August 2018, DOI 10.17487/RFC8446' },
|
||||
});
|
||||
function sources(entries) { return '<ul>' + entries.map(({ key, use, boundary }) => { const ref = REFERENCES[key]; return '<li><a href="' + ref.url + '" target="_blank" rel="noopener noreferrer">' + escapeHtml(ref.title) + '</a> — ' + escapeHtml(ref.version) + '. ' + escapeHtml(use) + ' Граница: ' + escapeHtml(boundary) + '</li>'; }).join('') + '</ul>'; }
|
||||
|
||||
const FIXED_HTTP_TLS_CASES = deepFreeze({
|
||||
'planned-http-tls-question-v1': { id: 'planned-http-tls-question-v1', planDate: '2027-05', sourceCutoff: '2026-07-31', protocolQuestion: { id: 'named-http-tls-boundary-question', version: 'named-protocol-version-question', statement: 'synthetic-only-no-endpoint-or-client' }, evidence: { state: 'not-collected', strength: 'input-bounded', command: 'not-run', endpoint: 'not-collected', clientConfig: 'not-collected', trace: 'not-collected' }, requestedConclusion: 'synthetic-plan-hand-off', boundary: 'Fixed in-memory planning literal. No HTTP request, TLS handshake, socket, endpoint, client configuration, certificate, command, trace, error, clock, file, environment, secret, telemetry, production system or customer data is read, created, changed or inferred.' },
|
||||
'undated-plan-or-cutoff-v1': { id: 'undated-plan-or-cutoff-v1', planDate: '', sourceCutoff: '2026-07-31', protocolQuestion: { id: 'named-http-tls-boundary-question', version: 'named-protocol-version-question', statement: 'synthetic-only-no-endpoint-or-client' }, evidence: { state: 'not-collected', strength: 'input-bounded', command: 'not-run', endpoint: 'not-collected', clientConfig: 'not-collected', trace: 'not-collected' }, requestedConclusion: 'synthetic-plan-hand-off', boundary: 'Negative fixed literal only.' },
|
||||
'unnamed-protocol-or-version-question-v1': { id: 'unnamed-protocol-or-version-question-v1', planDate: '2027-05', sourceCutoff: '2026-07-31', protocolQuestion: { id: '', version: '', statement: 'synthetic-only-no-endpoint-or-client' }, evidence: { state: 'not-collected', strength: 'input-bounded', command: 'not-run', endpoint: 'not-collected', clientConfig: 'not-collected', trace: 'not-collected' }, requestedConclusion: 'synthetic-plan-hand-off', boundary: 'Negative fixed literal only.' },
|
||||
'evidence-stronger-than-input-v1': { id: 'evidence-stronger-than-input-v1', planDate: '2027-05', sourceCutoff: '2026-07-31', protocolQuestion: { id: 'named-http-tls-boundary-question', version: 'named-protocol-version-question', statement: 'synthetic-only-no-endpoint-or-client' }, evidence: { state: 'claimed', strength: 'stronger-than-input', command: 'not-run', endpoint: 'not-collected', clientConfig: 'not-collected', trace: 'not-collected' }, requestedConclusion: 'synthetic-plan-hand-off', boundary: 'Negative fixed literal only.' },
|
||||
'hidden-command-endpoint-or-config-v1': { id: 'hidden-command-endpoint-or-config-v1', planDate: '2027-05', sourceCutoff: '2026-07-31', protocolQuestion: { id: 'named-http-tls-boundary-question', version: 'named-protocol-version-question', statement: 'synthetic-only-no-endpoint-or-client' }, evidence: { state: 'not-collected', strength: 'input-bounded', command: 'hidden', endpoint: 'not-collected', clientConfig: 'not-collected', trace: 'not-collected' }, requestedConclusion: 'synthetic-plan-hand-off', boundary: 'Negative fixed literal only.' },
|
||||
'disallowed-positive-network-conclusion-v1': { id: 'disallowed-positive-network-conclusion-v1', planDate: '2027-05', sourceCutoff: '2026-07-31', protocolQuestion: { id: 'named-http-tls-boundary-question', version: 'named-protocol-version-question', statement: 'synthetic-only-no-endpoint-or-client' }, evidence: { state: 'not-collected', strength: 'input-bounded', command: 'not-run', endpoint: 'not-collected', clientConfig: 'not-collected', trace: 'not-collected' }, requestedConclusion: 'handshake-succeeded', boundary: 'Negative fixed literal only.' },
|
||||
});
|
||||
|
||||
export function createFixedHttpTlsCase(id = 'planned-http-tls-question-v1') { const value = FIXED_HTTP_TLS_CASES[id]; return value ? deepFreeze(cloneFixed(value)) : undefined; }
|
||||
function stop(status, reason, nextAction) { return deepFreeze({ status, reason, nextAction, productionEffect: 'not-attempted' }); }
|
||||
export function assessFixedHttpTlsPlan(input) {
|
||||
if (!Object.values(FIXED_HTTP_TLS_CASES).some((item) => JSON.stringify(item) === JSON.stringify(input))) return stop('stop-unknown-fixed-input', 'input-is-not-a-known-named-fixed-literal', 'select-a-named-fixed-case');
|
||||
if (input.planDate !== '2027-05' || input.sourceCutoff !== '2026-07-31') return stop('stop-undated-plan-or-cutoff', 'may-2027-plan-and-july-2026-cutoff-are-required', 'name-2027-05-and-2026-07-31');
|
||||
if (!input.protocolQuestion?.id || !input.protocolQuestion?.version || !input.protocolQuestion?.statement) return stop('stop-unnamed-protocol-or-version-question', 'protocol-and-version-question-must-be-named-synthetic-literals', 'name-the-protocol-version-question');
|
||||
if (input.evidence?.state !== 'not-collected' || input.evidence?.strength !== 'input-bounded') return stop('stop-evidence-stronger-than-input', 'no-evidence-may-be-claimed-or-stronger-than-the-fixed-input', 'keep-evidence-not-collected-and-input-bounded');
|
||||
if (input.evidence?.command !== 'not-run' || input.evidence?.endpoint !== 'not-collected' || input.evidence?.clientConfig !== 'not-collected' || input.evidence?.trace !== 'not-collected') return stop('stop-hidden-command-endpoint-or-config', 'commands-endpoints-client-config-and-traces-must-remain-not-run-or-not-collected', 'record-only-not-run-state-limits');
|
||||
if (input.requestedConclusion !== 'synthetic-plan-hand-off') return stop('stop-disallowed-positive-network-conclusion', 'a-handshake-http-or-production-positive-conclusion-is-forbidden', 'use-synthetic-plan-hand-off');
|
||||
return deepFreeze({ status: 'synthetic-plan-hand-off', caseId: input.id, protocolQuestion: deepFreeze(cloneFixed(input.protocolQuestion)), evidence: deepFreeze(cloneFixed(input.evidence)), boundary: input.boundary, productionEffect: 'not-attempted', nextAction: 'give-the-question-and-state-limits-to-a-future-evidence-owner' });
|
||||
}
|
||||
export function runFixedHttpTlsFixture() { const expected = [['planned-http-tls-question-v1', 'synthetic-plan-hand-off'], ['undated-plan-or-cutoff-v1', 'stop-undated-plan-or-cutoff'], ['unnamed-protocol-or-version-question-v1', 'stop-unnamed-protocol-or-version-question'], ['evidence-stronger-than-input-v1', 'stop-evidence-stronger-than-input'], ['hidden-command-endpoint-or-config-v1', 'stop-hidden-command-endpoint-or-config'], ['disallowed-positive-network-conclusion-v1', 'stop-disallowed-positive-network-conclusion']]; const checks = expected.map(([id, status]) => ({ id, expected: status, actual: assessFixedHttpTlsPlan(createFixedHttpTlsCase(id)).status })); const sample = createFixedHttpTlsCase(); return deepFreeze({ passed: checks.filter((item) => item.expected === item.actual).length, total: checks.length, accepted: checks.every((item) => item.expected === item.actual) && Object.isFrozen(sample) && Object.isFrozen(sample.protocolQuestion) && Object.isFrozen(sample.evidence), checks: deepFreeze(checks) }); }
|
||||
|
||||
function revision(meta, parts, referenceEntries) { const contentHtml = parts.join('') + h2('Проверяемые источники') + sources(referenceEntries); const proseLength = bodyText(contentHtml).length; if (proseLength < 5000 || proseLength > 15000) throw new Error(meta.slug + ': body length ' + proseLength); return deepFreeze({ ...meta, contentHtml, proseLength }); }
|
||||
const refs = [
|
||||
{ key: 'rfc9110', use: 'Определяет семантические части HTTP сообщения и границы интерпретации поля.', boundary: 'Не подтверждает ответ конкретного origin, proxy, клиента, endpoint или операции.' },
|
||||
{ key: 'rfc9112', use: 'Даёт первичную спецификацию framing HTTP/1.1, опубликованную до cutoff.', boundary: 'Не доказывает, какой wire-format или ошибка будут у неназванного соединения.' },
|
||||
{ key: 'rfc9114', use: 'Фиксирует модель HTTP/3 и её привязку к QUIC как справочный механизм.', boundary: 'Не делает утверждений о доступности HTTP/3 в каком-либо клиенте или сервисе.' },
|
||||
{ key: 'rfc8446', use: 'Даёт первичную спецификацию TLS 1.3 и сообщений handshake.', boundary: 'Не заменяет certificate chain, policy, trace, negotiated parameters или результат handshake.' },
|
||||
];
|
||||
|
||||
const practice = revision({ slug: 'editorial-2027-05-practice-http-tls-guide', title: 'Полевой справочник HTTP и TLS: практическая карта вопроса', categories: ['Инженерные практики', 'Сети'], cover: '/assets/editorial/2027/http-tls-guide-2027-handshake-header-map.svg', excerpt: 'План на май 2027: как поставить HTTP/TLS-вопрос без подмены его сетевым результатом.', readingMinutes: 19 }, [
|
||||
p('Самая дорогая ошибка при разборе HTTP/TLS — начать с команды и закончить чужим выводом: «сервер ответил», «сертификат плохой» или «TLS работает». Без названного протокольного вопроса эти фразы склеивают уровни: HTTP semantics, message framing, TLS negotiation, DNS, маршрут и политика клиента. Цена — часы в неверной команде и ложное исправление, которое меняет не ту границу. В полевом справочнике сначала должна появиться карта вопроса, а не инструмент в терминале.'),
|
||||
p('Редакторская дата — 2026-07-31; май 2027 ещё не наступил. Поэтому P111 не содержит controlled handshake, HTTP trace, cURL/OpenSSL run, endpoint, client config, certificate, error или production result. Это строго plan/scenario на <code>2027-05</code> с source cutoff <code>2026-07-31</code>. Единственный положительный outcome — <code>synthetic-plan-hand-off</code>; у него всегда <code>productionEffect: not-attempted</code>. Любая приятная фраза о соединении здесь была бы выдуманным сетевым отчётом.'),
|
||||
h2('Карта начинается с вопроса, а не с симптома'),
|
||||
p('Практический вопрос должен иметь два имени: что различаем и для какой версии это различение формулируем. В fixed literal это <code>named-http-tls-boundary-question</code> и <code>named-protocol-version-question</code>. Это не hostname, не URI, не ALPN value и не название библиотеки. Такие placeholders нарочно достаточно бедны: они не позволяют незаметно привезти endpoint, команду или configuration под видом «контекста». Если protocol/version question не назван, модель останавливается; default вроде «наверное, HTTPS» уже является неподтверждённой подстановкой.'),
|
||||
p('Вопрос удобно делить на четыре плоскости. Семантика HTTP отвечает, что означает method, target, status и field в сообщении. Framing отвечает, где сообщение начинается и заканчивается в выбранном HTTP mapping. TLS handshake отвечает, какие сообщения и параметры могут быть согласованы до прикладного обмена. Доставка и policy отвечают за то, что находится вокруг этого: имена, маршрутизация, trust store, ограничения клиента и промежуточные узлы. Отсутствие наблюдения в одной плоскости не делает фактом вывод о другой.'),
|
||||
figure('/assets/editorial/2027/http-tls-guide-2027-handshake-header-map.svg', 'Карта вопроса HTTP и TLS: отдельные блоки HTTP-семантики, framing, TLS handshake и клиентской policy сходятся только в synthetic hand-off; красные блоки запрещают endpoint, команду и вывод о соединении.', 'Схема помогает назвать границу будущего вопроса. Это не trace, не handshake и не результат обращения к сети.'),
|
||||
table('Версионированная карта вопроса на 2027-05', ['Плоскость', 'Что можно назвать сейчас', 'Что не известно', 'Что передать owner'], [
|
||||
['HTTP semantics', 'method/status/header question', 'конкретное сообщение и ответ', 'правило интерпретации'],
|
||||
['HTTP framing', 'named HTTP version question', 'байты, порядок и длины', 'какой mapping требуется уточнить'],
|
||||
['TLS handshake', 'named TLS version question', 'negotiation и certificate', 'какой class evidence нужен'],
|
||||
['Client policy', 'boundary question', 'trust, proxy, retry, config', 'полномочия и provenance'],
|
||||
['Итог', 'synthetic plan', 'endpoint, trace, error, result', 'hand-off без network action'],
|
||||
]),
|
||||
h2('Заголовок не является trace'),
|
||||
p('Поле заголовка имеет имя, значение и определённую семантику в контексте сообщения; это не означает, что мы видели такое поле на wire. RFC 9110 полезен для разделения semantics от конкретной реализации: он помогает сформулировать, что будущий вопрос относится к request, response или representation. Но даже точное правило спецификации не сообщит, какое поле добавит intermediary, что нормализует библиотека и в какой момент приложение увидит значение. Поэтому нельзя писать «этот header был причиной» без материала, который разрешён и сохранён в новом scope.'),
|
||||
p('То же относится к HTTP/1.1 и HTTP/3. RFC 9112 описывает HTTP/1.1 framing, RFC 9114 — HTTP/3 поверх QUIC. Из этого следует, что название версии меняет допустимый механизм разбора; не следует, что выбранный клиент использовал один из них. Практический справочник не выбирает fallback, не предлагает «универсальный» request и не рисует bytes. Пока нет именованной версии и authorised evidence, правильная инструкция короче: назвать вопрос и не выдавать формат за наблюдение.'),
|
||||
h2('TLS — отдельный слой причинности'),
|
||||
p('TLS 1.3 описан в RFC 8446 как последовательность протокольных сообщений и проверок, но текст RFC не превращается в report о неизвестном peer. Слова certificate, handshake failure и negotiated version особенно опасны: они звучат технически, однако без certificate chain, configuration и trace дают только предположение. Сценарий P111 не хранит и не получает эти объекты. Он не называет algorithm, cipher suite, SNI, ALPN, CA store или параметр команды, потому что все они были бы не нейтральными примерами, а скрытой client configuration.'),
|
||||
p('Плановая граница полезна именно своей строгостью. Будущий owner может решить, что вопрос на самом деле про HTTP semantics и TLS-данные не нужны; может решить обратное; может остановить discovery из-за отсутствия полномочий. Нельзя компенсировать эту неопределённость «типичной» настройкой cURL или OpenSSL. Поведение инструментов меняется по release и build, а P111 не фиксирует проверенный exact release source для такого поведения. В перечне будущих средств остаётся только состояние: <code>command: not-run</code>, <code>trace: not-collected</code>.'),
|
||||
h2('Безопасный runnable literal'),
|
||||
code("import { createFixedHttpTlsCase, assessFixedHttpTlsPlan } from './upgrade-2027-05.mjs';\n\nconst plan = createFixedHttpTlsCase('planned-http-tls-question-v1');\nconst handOff = assessFixedHttpTlsPlan(plan);\nconsole.log({ status: handOff.status, effect: handOff.productionEffect });\n// { status: 'synthetic-plan-hand-off', effect: 'not-attempted' }"),
|
||||
p('Пример запускаем, но он не является сетевой проверкой. Factory делает JSON clone named fixed literal и затем recursive deep freeze; evaluator принимает только JSON-равный known literal и fail-closed закрывает всё остальное. Вызов не использует socket, fetch, child process, file, environment, clock, certificate store или telemetry. Он демонстрирует исключительно редакционный контракт: исходные данные ограничены literal, следовательно evidence не может стать сильнее input и итог не может превратиться в handshake result.'),
|
||||
h2('Порядок действий для будущего владельца'),
|
||||
ol(['Сначала зафиксировать <code>planDate: 2027-05</code> и <code>sourceCutoff: 2026-07-31</code>; недатированный сценарий закрыть.', 'Назвать protocol question и version question, не подменяя их endpoint, URL, hostname или именем клиента.', 'Разнести desired inference по HTTP semantics, framing, TLS и client policy; не делать один симптом общей причиной.', 'Оставить cURL/OpenSSL и любые traces только в списке <code>not-run/not-collected</code>, без команды и конфигурации.', 'Проверить fixed literal; evidence stronger than input, hidden command/endpoint/config и positive network conclusion должны вернуть stop.', 'Передать question contract будущему evidence owner, который отдельно решит полномочия, минимальный набор данных и метод.' ]),
|
||||
h2('Как не потерять версию в формулировке'),
|
||||
p('Версия в карте — не число для украшения. Она отвечает на вопрос, какой именно нормативный vocabulary допустимо использовать в будущем contract. HTTP/1.1 framing нельзя тихо подменить общим словом «HTTP», а TLS 1.3 нельзя описать как абстрактное «шифрование». При этом названная версия не становится observed negotiation. Два утверждения живут на разных уровнях: первое — о границе формулировки, второе — о факте конкретного взаимодействия. План имеет право только на первое.'),
|
||||
p('Полезный приём для owner — выписать рядом «вопрос версии» и «наблюдение версии», не смешивая поля. У первого допустимы synthetic labels и ссылка на RFC, у второго потребовались бы собственные provenance, timestamp, collection policy и rule интерпретации. Если эти столбцы оказались одинаковыми, вероятно, в draft уже проникли данные. В P111 такого перехода нет: version остаётся именем будущего различения, а не свойством клиента или peer.'),
|
||||
p('Это различение экономит усилие при эскалации. Вместо спора, «почему не проверить прямо сейчас», hand-off показывает недостающие полномочия: target не назван, client context не согласован, сохранение output не определено. Тогда решение может быть не только запуском. Команда вправе сузить вопрос, перенести его в другой контур или признать, что стандартного знания достаточно. Справочник не навязывает проверку как обязательный ритуал; он не даёт ритуалу замаскироваться под результат.'),
|
||||
p('Нельзя и переносить на будущее сегодняшнюю форму вопроса как неизменную. К маю 2027 могут измениться продуктовая цель, ownership, допустимый канал наблюдения или сам смысл риска. Cutoff фиксирует только то, какие источники использованы для редакционного vocabulary; он не замораживает реальную систему. Поэтому hand-off не содержит заранее выбранного client или target и не обещает, что будущая работа обязана следовать тем же шагам. Его ценность — передать точку честного старта, а не создать скрытый проектный план.'),
|
||||
p('Практический reader может использовать карту уже сейчас как редакционную проверку вопроса. Достаточно спросить: где в тексте граница HTTP, где framing, что относится к TLS, кто определяет client policy, и какое слово создаёт наблюдение без источника. Если на один из вопросов нет ответа, это не повод добавить знакомую техническую деталь. Это повод вернуть карточку к именованному placeholder. В таком виде она остаётся применимой к будущему scope и не расходует доверие к документации.'),
|
||||
p('Так карта остаётся рабочей и после того, как будущий scope изменит инструменты или вовсе откажется от их использования: она описывает не команду, а порядок различения.'),
|
||||
h2('Ограничения и следующий шаг'),
|
||||
p('Этот текст не обучает обходить TLS-проверки, не задаёт client flags и не рекомендует отключать валидацию. Он также не диагностирует 4xx/5xx, reset, timeout, mismatch или любую другую ошибку: таких error здесь нет. RFC-источники до cutoff дают язык для точного будущего вопроса, но не дают ответ о конкретной сети. Даже существование method или handshake message в стандарте не подтверждает, что оно применимо к неназванной системе.'),
|
||||
p('Следующий шаг — сохранить hand-off как ограниченную карточку до отдельного authorised scope. В новом artefact будущий owner сможет определить, какой evidence допустим, как отделить observation от inference и какие данные нельзя сохранять. До этого нельзя превратить вопрос в endpoint, client config или production action. Здесь завершён только безопасный переход: вопрос имеет дату, cutoff, границу и следующую роль; production effect не пытались получить.'),
|
||||
], refs);
|
||||
|
||||
const mechanism = revision({ slug: 'editorial-2027-05-mechanism-http-tls-guide', title: 'Полевой справочник HTTP и TLS: границы inference', categories: ['Инженерные практики', 'Сети'], cover: '/assets/editorial/2027/http-tls-guide-2027-symptom-boundary-matrix.svg', excerpt: 'План на май 2027: как отличить классы HTTP/TLS-ошибок от выводов, которых evidence не поддерживает.', readingMinutes: 20 }, [
|
||||
p('Дорогая диагностическая ошибка — объявить один наблюдаемый симптом причиной на соседнем уровне: HTTP status назвать TLS отказом, ошибку проверки certificate — свойством заголовка, а отсутствие response — доказательством сервера. Такая склейка заставляет менять policy, proxy или приложение вслепую; цена — длинный цикл повторной работы и риск затронуть не ту границу. Механизм нужен не для «быстрого диагноза», а для точного запрета на inference, который не следует из входа.'),
|
||||
p('На 2026-07-31 у P111 нет наблюдаемого симптома, HTTP trace, controlled handshake, endpoint, client config, certificate, command output или production result; май 2027 ещё впереди. Поэтому это plan/scenario на <code>2027-05</code>, cutoff <code>2026-07-31</code>. Мы разбираем классы различения, а не фактические errors. Positive outcome только <code>synthetic-plan-hand-off</code> с <code>productionEffect: not-attempted</code>; «успешно», «исправлено» и «соединение установлено» запрещены.'),
|
||||
h2('Inference имеет потолок'),
|
||||
p('Evidence не должен быть сильнее input. Если input — named question и state <code>not-collected</code>, допустимый output — только вопрос с ограничениями. Он не может вдруг содержать negotiated protocol, header set, certificate finding или error classification. Это не избыточная осторожность: поздний reader не видит, где закончился вход и началась уверенная интерпретация. В validator case <code>evidence-stronger-than-input-v1</code> специально заявляет claimed evidence и закрывается с <code>stop-evidence-stronger-than-input</code>.'),
|
||||
p('Ошибка класса и ошибка механизма — не одно и то же. «HTTP semantic mismatch» описывает расхождение между ожидаемой и допустимой интерпретацией сообщения. «HTTP framing boundary» относится к способу выделить сообщение в конкретном mapping. «TLS negotiation boundary» относится к протокольному согласованию до HTTP semantics. «Client trust/policy boundary» относится к тому, что клиент разрешает использовать. Эти labels полезны как future buckets, но без конкретного input ни один bucket не заполнен и ни один не является диагнозом.'),
|
||||
figure('/assets/editorial/2027/http-tls-guide-2027-symptom-boundary-matrix.svg', 'Матрица границ inference: HTTP-семантика, framing, TLS negotiation и client policy сопоставлены с допустимым доказательством; красная колонка запрещает вывод о соседнем уровне и positive conclusion.', 'Матрица различает классы вопросов и не сообщает об ошибке реального клиента, сервера или сертификата.'),
|
||||
table('Граница между симптомом и выводом', ['Класс будущего вопроса', 'Минимальное evidence', 'Что ещё не следует', 'Безопасное действие сейчас'], [
|
||||
['HTTP semantics', 'явно разрешённый message context', 'TLS причина или endpoint state', 'сформулировать semantic rule'],
|
||||
['HTTP framing', 'versioned wire mapping', 'значение header или policy', 'назвать HTTP version question'],
|
||||
['TLS negotiation', 'разрешённый handshake material', 'HTTP status и app behaviour', 'назвать TLS version question'],
|
||||
['Trust / client policy', 'явная client configuration', 'peer identity или network fault', 'отделить policy от peer'],
|
||||
['P111 сейчас', 'fixed literal only', 'любой error/result', 'synthetic hand-off'],
|
||||
]),
|
||||
h2('HTTP semantics не доказывает транспорт'),
|
||||
p('RFC 9110 задаёт semantics message components. Он помогает проверить будущую формулировку вида «какое значение status или field допустимо интерпретировать», но не отвечает, достигло ли сообщение origin и не показывает путь. Даже если future owner имеет HTTP response, статус сам по себе не описывает TLS handshake, name resolution, retry policy или действия intermediary. Следовательно, нельзя начинать с слова «HTTP error» и сразу менять trust or transport assumptions; сначала нужно отделить реальный message context от гипотезы о причинах.'),
|
||||
p('RFC 9112 нужен, когда сам вопрос относится к HTTP/1.1 message framing. Он не является доказательством bytes конкретной сессии и не говорит, что некоторый header «сломал соединение». RFC 9114 аналогично не превращает knowledge об HTTP/3 в evidence, что выбранный клиент договорился о QUIC. Версия должна быть частью именованного вопроса, иначе одинаковая терминология начинает скрывать разные механизмы. Именно поэтому unnamed protocol/version literal не получает guessed fallback, а возвращает stop.'),
|
||||
h2('TLS handshake не является HTTP response'),
|
||||
p('RFC 8446 фиксирует TLS 1.3 protocol. Он позволяет описать, какие классы данных вообще относятся к handshake, но не подтверждает ни один конкретный parameter. Certificate validation, server-name policy, local trust anchors, session resumption и application request остаются разными объектами. У future investigation может быть несколько независимых ограничений, однако P111 не называет их значений. Попытка вставить сюда «обычный» endpoint или client option сразу превратила бы механизм в скрытый experiment.'),
|
||||
p('Различение классов полезно и при будущей ошибке. Например, сообщение приложения о невозможности получить response не разрешает назвать certificate причиной; certificate-related failure не разрешает приписать HTTP status; mismatch в semantics не говорит, что TLS настроен неверно. Это не отрицание причинных цепочек — это отказ сокращать цепочку без материалов. Будущий owner должен документировать, какое observation поддерживает какой inference и где inference заканчивается. P111 передаёт именно это правило, а не готовый troubleshooting recipe.'),
|
||||
h2('Скрытая конфигурация делает ошибку неразличимой'),
|
||||
p('Command, endpoint и client configuration — часть evidence method, а не декоративное приложение. Но в P111 они даже не собираются. Когда их значения скрыты, сторонний reader не знает, какая версия протокола была запрошена, какая policy влияла на ход, что мог изменить proxy и какая команда сформировала output. Это не приглашение опубликовать их здесь: правильное состояние — <code>command: not-run</code>, endpoint/config/trace <code>not-collected</code>. Case <code>hidden-command-endpoint-or-config-v1</code> fail-closed прекращает hand-off.'),
|
||||
p('Отдельно запрещено выдумывать current behavior cURL или OpenSSL. У P111 нет exact pinned release material, который проверяет конкретную версию, build и option semantics; значит, текст не утверждает, как инструмент поведёт себя. Названия средств могут присутствовать только как очередь будущих средств, не как instruction. Такой предел спасает от самой коварной ошибки: читатель видит знакомую команду и принимает её как допустимую configuration для неизвестного production context.'),
|
||||
h2('Runnable пример отказа'),
|
||||
code("import { createFixedHttpTlsCase, assessFixedHttpTlsPlan } from './upgrade-2027-05.mjs';\n\nconst tooStrong = createFixedHttpTlsCase('evidence-stronger-than-input-v1');\nconst result = assessFixedHttpTlsPlan(tooStrong);\nconsole.log([result.status, result.productionEffect].join(' | '));\n// stop-evidence-stronger-than-input | not-attempted"),
|
||||
p('В этом runnable fragment нет wire activity. Он берёт named in-memory literal, который JSON-clone factory отделила от caller и recursive deep freeze зафиксировала до оценки. Validator допускает только exactly known fixed inputs, поэтому произвольный object не может принести «наблюдение» вне списка. Вывод сообщает boundary модели, а не сетевую ошибку. Никакого cURL/OpenSSL subprocess, fetch, socket, certificate parser, file, environment или clock код не использует.'),
|
||||
h2('Порядок будущего различения'),
|
||||
ol(['Зафиксировать дату плана и cutoff; не называть будущую работу фактом.', 'Назвать protocol/version question и отделить HTTP semantics, framing, TLS и client policy.', 'Для каждого будущего observation заранее записать, какой inference он поддерживает, а какой нет.', 'Не принимать evidence, сильнее разрешённого input; claimed result в P111 всегда stop.', 'Не скрывать command, endpoint, client config или trace за пересказом; здесь все они not-run/not-collected.', 'Передать matrix будущему owner без diagnosis, remediation или positive network conclusion.' ]),
|
||||
h2('Почему table не заменяет причинную запись'),
|
||||
p('Матрица с четырьмя строками легко читается как классификатор, хотя ей нельзя быть классификатором. Она не получает строку лога и не возвращает label. Её задача уже: напомнить, что для каждого возможного утверждения нужна запись «вход → допустимое следствие → запрещённое следствие». Без среднего звена symptom превращается в вывод, а запрещённое следствие исчезает из обсуждения. Именно тогда привычные слова вроде «транспортная ошибка» начинают означать всё сразу.'),
|
||||
p('В реальном authorised artefact причинная запись должна быть симметричной. Не только observation должен вести к hypothesis; hypothesis обязана перечислить, какой другой material мог бы её опровергнуть. Для HTTP semantics это может быть другой message context, для policy — иное правило клиента, для TLS — другой участок протокольных данных. P111 не перечисляет такие материалы по имени, чтобы не создать ложный collection plan. Он передаёт требование к форме будущей записи, не её заполненный экземпляр.'),
|
||||
p('Особая польза этого подхода — отделить remediation от diagnosis. Даже верно определённый класс не предписывает, что надо менять: порядок исправления зависит от владельца, риска и contract вокруг системы. Поэтому phrase «значит, надо отключить проверку» здесь была бы одновременно неверным inference и опасной рекомендацией. Механизм останавливается раньше: он требует показать связь между разрешёнными входами и утверждением, после чего другой scope сможет обсуждать действие.'),
|
||||
p('Граница useful и для коммуникации между ролями. Автор приложения может сформулировать expected semantics, security owner — правила доверия, platform owner — ограничения маршрута, а investigator — минимальный evidence contract. Ни одна роль не должна заполнять чужой столбец догадкой ради короткого ответа. В P111 эти роли не назначаются и не существует реального incident; matrix лишь показывает, что их утверждения имеют различный тип. Это снижает риск, что техническое слово будет принято за право сделать production change.'),
|
||||
h2('Ограничения и следующий шаг'),
|
||||
p('Матрица не заменяет capture, security review, protocol implementation review или incident process. Она не классифицирует настоящие errors, не назначает владельца certificate и не выбирает точку наблюдения. Даже при появлении данных классы могут пересекаться: один application symptom способен иметь несколько независимых причин. Механизм полезен до сбора тем, что запрещает писать результат заранее и заставляет определить доказательную границу.'),
|
||||
p('Следующий шаг — будущему owner создать отдельный scope с разрешённым input, version pins, redaction rules и явным цепочечным описанием observation → inference. Этот новый scope может использовать другие инструменты, но он не является продолжением P111 по умолчанию. До такого решения единственная корректная публикация — карта границ и synthetic hand-off. Она не делает TLS или HTTP проще; она делает честным место, где заканчивается знание.'),
|
||||
], refs);
|
||||
|
||||
const field = revision({ slug: 'editorial-2027-05-field-http-tls-guide', title: 'Полевой справочник HTTP и TLS: передача synthetic evidence', categories: ['Инженерные практики', 'Сети'], cover: '/assets/editorial/2027/http-tls-guide-2027-evidence-handoff-loop.svg', excerpt: 'План на май 2027: hand-off вопроса об HTTP/TLS без поддельного сетевого отчёта.', readingMinutes: 18 }, [
|
||||
p('Самая дорогая ошибка в полевой передаче — назвать вопрос отчётом: написать, что handshake проверен, header найден или ошибка воспроизведена, хотя получатель видит лишь пересказ. Такой артефакт быстро становится «фактом» в очереди, влияет на владельцев и может запустить ненужное изменение policy. Цена выше одной неверной заметки: provenance теряется, а будущая проверка вынуждена спорить с несуществующим evidence вместо того, чтобы начать с ясной постановки.'),
|
||||
p('P111 не является network report. На редакторскую дату 2026-07-31 май 2027 ещё не наступил; это plan/scenario на <code>2027-05</code> с source cutoff <code>2026-07-31</code>. Здесь не было endpoint, certificate, client config, cURL/OpenSSL run, HTTP trace, controlled handshake, production result или error. Field focus означает только synthetic evidence hand-off. Разрешённый positive outcome — <code>synthetic-plan-hand-off</code>, а <code>productionEffect: not-attempted</code> остаётся на любом пути.'),
|
||||
h2('Передавать вопрос, состояние и предел'),
|
||||
p('Хорошая synthetic карточка содержит не «что случилось», а что ещё нельзя утверждать. В ней есть planDate, sourceCutoff, named protocol/version question, evidence state и next action. Поле <code>state: not-collected</code> не значит «лог потеряли» и не намекает на частную проверку; оно означает, что P111 ничего не собирал. Поле <code>strength: input-bounded</code> не измеряет качество будущих данных, а запрещает hand-off стать богаче собственных literals. Это небольшой контракт, зато reader может проверить каждую его границу.'),
|
||||
p('Предел особенно важен для команд и traces. Слова «попробуйте cURL», «снимите OpenSSL handshake» обычно выглядят безобидно, но в реальной сети они уже задают endpoint, client policy, traffic и способ хранения output. В этом выпуске tools помещены только в очередь будущего решения: command <code>not-run</code>, endpoint/clientConfig/trace <code>not-collected</code>. Мы не даём command line, не описываем certificate, не придумываем error и не сообщаем, что чей-то сервер принимает или отклоняет соединение.'),
|
||||
figure('/assets/editorial/2027/http-tls-guide-2027-evidence-handoff-loop.svg', 'Петля передачи synthetic evidence: дата и named protocol/version question проходят fail-closed gates для evidence, command, endpoint, config и positive conclusion; только затем вопрос передаётся будущему owner.', 'Петля изображает in-memory validation и не является журналом сети, HTTP trace или результатом TLS handshake.'),
|
||||
table('Контракт synthetic HTTP/TLS hand-off', ['Поле', 'Состояние в P111', 'Что получает owner', 'Чего это не доказывает'], [
|
||||
['Время', '2027-05 / cutoff 2026-07-31', 'редакционную границу', 'что работа в мае выполнена'],
|
||||
['Вопрос', 'named synthetic protocol/version', 'объект уточнения', 'protocol choice или peer'],
|
||||
['Evidence', 'not-collected/input-bounded', 'предел inference', 'header, handshake или error'],
|
||||
['Средства', 'not-run/not-collected', 'список будущих ограничений', 'command/output/config'],
|
||||
['Вывод', 'synthetic-plan-hand-off', 'следующий decision point', 'success, remediation, production effect'],
|
||||
]),
|
||||
h2('Provenance сильнее убедительного тона'),
|
||||
p('У поля evidence должен быть не только label, но и допустимая сила. В сценарии incoming literal — единственный материал. Если evaluator вдруг возвращает certificate conclusion, конкретную protocol version или success claim, он делает output сильнее input. Именно такая подмена закрывается отдельным stop. Формулировка «кажется, это TLS» тоже не нейтральна: она приписывает классу будущей проверки больше, чем дано входом. Честная передача использует «требуется различить» вместо «обнаружено».'),
|
||||
p('Fail-closed важнее гладкого hand-off. Недатированный кейс не получает текущую дату автоматически: иначе future plan тихо превращается в report. Неназванный protocol/version question не получает conventional default: иначе «HTTP» и «TLS» становятся фоновой догадкой. Скрытая command/endpoint/config не получает placeholder: иначе future owner примет метод как уже заданный. В каждом из этих случаев stop — не ошибка пользователя, а сохранение права пересобрать вопрос до доступа к сети.'),
|
||||
h2('Стандарты задают vocabulary, не факт'),
|
||||
p('Pinned RFC 9110, RFC 9112, RFC 9114 и RFC 8446 опубликованы до cutoff и служат первичными источниками vocabulary: semantics HTTP, HTTP/1.1 framing, HTTP/3 and QUIC mapping, TLS 1.3 handshake. Они не дают контекст конкретного клиента. Нельзя взять определение из RFC и выдать его за сведения о current deployment; нельзя узнать из него certificate chain или точный error. Такая разница кажется очевидной в лаборатории, но исчезает в краткой полевой заметке — поэтому здесь она записана явно.'),
|
||||
p('P111 намеренно не утверждает current cURL/OpenSSL behavior. Для подобного утверждения нужен exact pinned release source и scope, который связывает release с конкретной допустимой конфигурацией; этого нет. Даже тогда documentation не была бы trace. Пока tools не разрешены, future owner получает не рецепт, а вопрос: какой инструмент вообще нужен, какие параметры являются чувствительными, какой output допустимо сохранить и что будет считаться недостаточным evidence.'),
|
||||
h2('Runnable контракт hand-off'),
|
||||
code("import { createFixedHttpTlsCase, assessFixedHttpTlsPlan } from './upgrade-2027-05.mjs';\n\nconst hiddenMethod = createFixedHttpTlsCase('hidden-command-endpoint-or-config-v1');\nconst result = assessFixedHttpTlsPlan(hiddenMethod);\nconsole.log({ status: result.status, effect: result.productionEffect });\n// { status: 'stop-hidden-command-endpoint-or-config', effect: 'not-attempted' }"),
|
||||
p('Пример демонстрирует отказ, а не результат подключения. Все значения fixed и in-memory; JSON clone не даёт caller менять исходный nested object, deep freeze закрепляет protocolQuestion и evidence, fail-closed evaluator отклоняет unknown input. В модуле нет network library, shell invocation, HTTP parser, TLS implementation, file read или process configuration. Поэтому runnable behaviour можно проверить локально без того, чтобы создать trace или попытаться воздействовать на production.'),
|
||||
h2('Порядок передачи'),
|
||||
ol(['Передать дату <code>2027-05</code> и cutoff <code>2026-07-31</code> как обязательную границу знания.', 'Передать только named protocol/version question и statement, не добавляя endpoint, hostname, certificate или client name.', 'Передать evidence как <code>not-collected/input-bounded</code>; не превращать отсутствие данных в нейтральный success.', 'Явно оставить cURL/OpenSSL, commands, traces и configuration в состоянии not-run/not-collected.', 'Проверить fail-closed stops для undated case, unnamed question, stronger evidence, hidden method и positive conclusion.', 'Отдать future evidence owner next action без queue mutation, ticket, test report, remediation или production change.' ]),
|
||||
h2('Минимальная карточка лучше ложной полноты'),
|
||||
p('Полевой hand-off часто пытаются сделать полезным добавлением «кажется очевидных» деталей. Появляется пример host, предполагаемый port, название trust store, фрагмент команды или краткое описание certificate. Для будущего расследования это кажется экономией времени, но provenance у таких деталей нулевой: читатель не знает, увидел ли автор их, получил из документа или просто дополнил по памяти. В P111 минимальная карточка сознательно не содержит ни одного такого ускорителя.'),
|
||||
p('Минимум не равен расплывчатости. Карточка обязана ясно назвать, что нужно различить, когда knowledge зафиксировано и какие состояния недопустимы. Поэтому fixed literal строже свободной заметки: у него есть exact id, date/cutoff, nested question, evidence state и disallowed conclusion. Неопределённость здесь структурирована. Owner не получит загадку «что-то с HTTPS»; он получит проверяемое ограничение, что пока нельзя рассказывать о peer, сообщении или инструменте.'),
|
||||
p('Такой формат облегчает и отказ. Получатель может вернуть hand-off с формулировкой «вопрос не имеет достаточного решения о доступе» без дискуссии о якобы собранном trace. Он может попросить автора уточнить цель на уровне приложения, а не сети. Он может передать вопрос security owner, не распространяя endpoint. Все эти действия оставляют исходный artefact истинным. Ложный отчёт, напротив, заставляет либо верить ему, либо повторять неизвестную методику, что дороже и опаснее.'),
|
||||
p('Карточку нельзя улучшать добавлением «вероятного» результата. Даже осторожное «ожидается нормальный handshake» создаёт асимметрию: следующий reader помнит прогноз, но не помнит, что evidence отсутствует. Аналогично phrase «должен вернуться header» звучит как план, но фактически обещает content неизвестного сообщения. В P111 будущему owner передаётся не ожидание output, а список условий, которые придётся определить до первого authorized observation. Это делает последующую проверку свободной отменить исходную гипотезу.'),
|
||||
p('Синтетический hand-off полезно хранить рядом с границами, а не рядом с логами. Его читатель не ищет в нём bytes или timestamp; он ищет, какую claim нельзя сделать и кому передать решение. Когда реальный scope появится, его artefact должен ссылаться на P111 лишь как на предшествующую постановку, а не как на evidence. Такая связь оставляет историческую цепочку понятной: сначала была редакционная дисциплина, затем — отдельно разрешённый сбор, если он вообще окажется необходим.'),
|
||||
p('Именно поэтому результат hand-off не измеряется количеством технических деталей. Его достаточно, когда другой человек может безопасно остановиться, сузить вопрос или открыть новый scope, не переписывая историю как будто проверка уже состоялась.'),
|
||||
p('Если этого предела нет, заметка перестаёт быть передачей и становится неаудируемым обещанием: её нельзя ни проверить, ни корректно отозвать без потери доверия. Поэтому правильная краткость здесь ценнее демонстративной технической полноты.'),
|
||||
h2('Что owner решает вне P111'),
|
||||
p('Если будущий владелец примет hand-off, ему понадобятся отдельные решения: нужен ли вообще сетевой scope, кто авторизует target, какая минимальная configuration допустима, какие поля trace нельзя сохранять, как redaction влияет на inference и кто проверяет вывод. Может оказаться, что вопрос надо закрыть без запуска, потому что нужный факт есть в другом авторитетном artefact или риск сбора не оправдан. P111 не подталкивает к инструменту и не создаёт обязанность «довести проверку до успеха».'),
|
||||
p('Следующий шаг — завести новый, явно авторизованный artefact только если owner выберет это действие. Он должен иметь собственную дату, source set, method, limits и result policy; его facts нельзя ретроспективно добавить в P111. До такого шага текущий итог уже полный: передан честно ограниченный вопрос. Полезность field hand-off не в том, что он производит network result, а в том, что он не даёт отсутствующему результату управлять последующими решениями.'),
|
||||
], refs);
|
||||
|
||||
export const revisions = deepFreeze([practice, mechanism, field]);
|
||||
export function verifyRevisionsAgainstFixture() { const fixture = runFixedHttpTlsFixture(); const articleChecks = revisions.map((item) => { const text = bodyText(item.contentHtml); return text.length >= 5000 && text.length <= 15000 && /(цен[аы]|стоимост|издержк|потер)/i.test(text.slice(0, 1100)) && /<table>/.test(item.contentHtml) && /<figure>/.test(item.contentHtml) && /<pre><code>/.test(item.contentHtml) && /<ol>/.test(item.contentHtml) && /2027-05/.test(text) && /2026-07-31/.test(text) && /productionEffect: not-attempted/.test(text); }); return deepFreeze({ passed: fixture.passed + articleChecks.filter(Boolean).length, total: fixture.total + articleChecks.length, accepted: fixture.accepted && articleChecks.every(Boolean), fixture, articleChecks, characters: Object.fromEntries(revisions.map((item) => [item.slug, bodyText(item.contentHtml).length])) }); }
|
||||
if (process.argv.includes('--verify-fixture')) { const result = verifyRevisionsAgainstFixture(); process.stdout.write(JSON.stringify(result, null, 2) + '\n'); if (!result.accepted) process.exitCode = 1; }
|
||||
if (process.argv.includes('--print-revisions')) process.stdout.write(JSON.stringify(revisions) + '\n');
|
||||
Reference in New Issue
Block a user