revise August 2027 security capstone articles
Build and deploy / deploy (push) Successful in 17s

This commit is contained in:
2026-07-31 20:50:21 +03:00
parent 88bb775c1f
commit a59796c5d3
7 changed files with 382 additions and 1 deletions
+281
View File
@@ -0,0 +1,281 @@
function escapeHtml(value) {
return String(value)
.replaceAll('&', '&')
.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((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({
ssdf: {
title: 'NIST SP 800-218 — Secure Software Development Framework (SSDF) Version 1.1',
url: 'https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-218.pdf',
version: 'NIST Special Publication 800-218, Version 1.1, February 2022, immutable dated PDF',
},
controls: {
title: 'NIST SP 800-53 Rev. 5 — Security and Privacy Controls for Information Systems and Organizations',
url: 'https://doi.org/10.6028/NIST.SP.800-53r5',
version: 'NIST Special Publication 800-53 Revision 5, final dated 10 December 2020, immutable PDF',
},
});
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_SECURITY_CARDS = deepFreeze({
'security-capstone-boundary-card-v1': {
id: 'security-capstone-boundary-card-v1', editorialDate: '2026-07-31', planningIssue: '2027-08', sourceCutoff: '2026-07-31',
asset: { label: 'synthetic-asset-label', classification: 'not-declared', boundary: 'not-mapped' },
path: { label: 'synthetic-path-question', states: 'not-mapped', assumptions: 'not-accepted' },
control: { coverage: 'not-selected', evidence: 'not-collected', deployment: 'not-attempted' },
handoff: { recipient: 'not-assigned', route: 'not-opened' }, requestedOutput: 'synthetic-plan-hand-off',
},
'security-capstone-bad-date-v1': {
id: 'security-capstone-bad-date-v1', editorialDate: '', planningIssue: '2027-08', sourceCutoff: '2026-07-31',
asset: { label: 'synthetic-asset-label', classification: 'not-declared', boundary: 'not-mapped' }, path: { label: 'synthetic-path-question', states: 'not-mapped', assumptions: 'not-accepted' }, control: { coverage: 'not-selected', evidence: 'not-collected', deployment: 'not-attempted' }, handoff: { recipient: 'not-assigned', route: 'not-opened' }, requestedOutput: 'synthetic-plan-hand-off',
},
'security-capstone-path-claimed-v1': {
id: 'security-capstone-path-claimed-v1', editorialDate: '2026-07-31', planningIssue: '2027-08', sourceCutoff: '2026-07-31',
asset: { label: 'synthetic-asset-label', classification: 'not-declared', boundary: 'not-mapped' }, path: { label: 'synthetic-path-question', states: 'claimed', assumptions: 'not-accepted' }, control: { coverage: 'not-selected', evidence: 'not-collected', deployment: 'not-attempted' }, handoff: { recipient: 'not-assigned', route: 'not-opened' }, requestedOutput: 'synthetic-plan-hand-off',
},
'security-capstone-control-claimed-v1': {
id: 'security-capstone-control-claimed-v1', editorialDate: '2026-07-31', planningIssue: '2027-08', sourceCutoff: '2026-07-31',
asset: { label: 'synthetic-asset-label', classification: 'not-declared', boundary: 'not-mapped' }, path: { label: 'synthetic-path-question', states: 'not-mapped', assumptions: 'not-accepted' }, control: { coverage: 'claimed', evidence: 'not-collected', deployment: 'not-attempted' }, handoff: { recipient: 'not-assigned', route: 'not-opened' }, requestedOutput: 'synthetic-plan-hand-off',
},
'security-capstone-evidence-claimed-v1': {
id: 'security-capstone-evidence-claimed-v1', editorialDate: '2026-07-31', planningIssue: '2027-08', sourceCutoff: '2026-07-31',
asset: { label: 'synthetic-asset-label', classification: 'not-declared', boundary: 'not-mapped' }, path: { label: 'synthetic-path-question', states: 'not-mapped', assumptions: 'not-accepted' }, control: { coverage: 'not-selected', evidence: 'claimed', deployment: 'not-attempted' }, handoff: { recipient: 'not-assigned', route: 'not-opened' }, requestedOutput: 'synthetic-plan-hand-off',
},
'security-capstone-positive-effect-v1': {
id: 'security-capstone-positive-effect-v1', editorialDate: '2026-07-31', planningIssue: '2027-08', sourceCutoff: '2026-07-31',
asset: { label: 'synthetic-asset-label', classification: 'not-declared', boundary: 'not-mapped' }, path: { label: 'synthetic-path-question', states: 'not-mapped', assumptions: 'not-accepted' }, control: { coverage: 'not-selected', evidence: 'not-collected', deployment: 'attempted' }, handoff: { recipient: 'not-assigned', route: 'not-opened' }, requestedOutput: 'security-improved',
},
});
export function createSecurityCapstoneCard(id = 'security-capstone-boundary-card-v1') {
const value = FIXED_SECURITY_CARDS[id];
return value ? deepFreeze(cloneFixed(value)) : undefined;
}
function stop(status, reason, nextAction) { return deepFreeze({ status, reason, nextAction, productionEffect: 'not-attempted' }); }
export function assessSecurityCapstoneCard(input) {
const known = Object.values(FIXED_SECURITY_CARDS).some((item) => JSON.stringify(item) === JSON.stringify(input));
if (!known) return stop('stop-unknown-fixed-literal', 'input-is-not-a-known-named-fixed-literal', 'select-a-named-fixed-literal');
if (input.editorialDate !== '2026-07-31' || input.planningIssue !== '2027-08' || input.sourceCutoff !== '2026-07-31') return stop('stop-temporal-boundary-required', 'editorial-date-planning-issue-and-source-cutoff-must-be-exact', 'restore-the-fixed-temporal-boundary');
if (input.asset.classification !== 'not-declared' || input.asset.boundary !== 'not-mapped' || input.path.states !== 'not-mapped' || input.path.assumptions !== 'not-accepted') return stop('stop-claimed-asset-or-path', 'asset-and-path-must-remain-unmapped-and-unaccepted', 'remove-claimed-asset-or-path');
if (input.control.coverage !== 'not-selected' || input.control.evidence !== 'not-collected') return stop('stop-claimed-control-or-evidence', 'control-coverage-and-evidence-must-remain-unselected', 'remove-control-or-evidence-claim');
if (input.control.deployment !== 'not-attempted' || input.handoff.recipient !== 'not-assigned' || input.handoff.route !== 'not-opened') return stop('stop-claimed-execution-or-owner', 'deployment-owner-and-route-cannot-be-inferred', 'keep-execution-and-assignment-unattempted');
if (input.requestedOutput !== 'synthetic-plan-hand-off') return stop('stop-disallowed-positive-result', 'future-security-scenario-cannot-claim-a-positive-result', 'use-synthetic-plan-hand-off');
return deepFreeze({ status: 'synthetic-plan-hand-off', cardId: input.id, planningIssue: input.planningIssue, asset: deepFreeze(cloneFixed(input.asset)), path: deepFreeze(cloneFixed(input.path)), control: deepFreeze(cloneFixed(input.control)), handoff: deepFreeze(cloneFixed(input.handoff)), productionEffect: 'not-attempted', nextAction: 'open-a-separate-authorized-scope-only-if-a-decision-needs-evidence' });
}
export function inspectAssetBoundaryLiteral() { const card = createSecurityCapstoneCard(); return deepFreeze({ asset: card.asset.label, boundary: card.asset.boundary, path: card.path.states, output: assessSecurityCapstoneCard(card).status }); }
export function inspectCoverageLiteral() { const card = createSecurityCapstoneCard(); return deepFreeze({ path: card.path.label, coverage: card.control.coverage, evidence: card.control.evidence, effect: assessSecurityCapstoneCard(card).productionEffect }); }
export function inspectSecurityHandoffLiteral() { const result = assessSecurityCapstoneCard(createSecurityCapstoneCard()); return deepFreeze({ status: result.status, recipient: result.handoff.recipient, nextAction: result.nextAction }); }
export function runSecurityCapstoneFixture() {
const expected = [
['security-capstone-boundary-card-v1', 'synthetic-plan-hand-off'], ['security-capstone-bad-date-v1', 'stop-temporal-boundary-required'], ['security-capstone-path-claimed-v1', 'stop-claimed-asset-or-path'], ['security-capstone-control-claimed-v1', 'stop-claimed-control-or-evidence'], ['security-capstone-evidence-claimed-v1', 'stop-claimed-control-or-evidence'], ['security-capstone-positive-effect-v1', 'stop-claimed-execution-or-owner'],
];
const checks = expected.map(([id, status]) => ({ id, expected: status, actual: assessSecurityCapstoneCard(createSecurityCapstoneCard(id)).status }));
const sample = createSecurityCapstoneCard();
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.asset) && Object.isFrozen(sample.control), checks: deepFreeze(checks) });
}
function revision(meta, parts, referenceEntries) {
const contentHtml = parts.join('') + h2('Проверяемые источники') + sources(referenceEntries);
const proseLength = bodyText(contentHtml).length;
if (proseLength < 10000 || proseLength > 13000) throw new Error(meta.slug + ': body length ' + proseLength);
return deepFreeze({ ...meta, contentHtml, proseLength });
}
const refs = [
{ key: 'ssdf', use: 'Даёт датированную официальную лексику практик безопасной разработки и коммуникации между участниками.', boundary: 'Не доказывает наличие практики, проверки, уязвимости, результата или контроля в этом сценарии.' },
{ key: 'controls', use: 'Помогает различать функцию контроля и уверенность в ней, не смешивая карту с подтверждением.', boundary: 'Не подтверждает, что какой-либо контроль выбран, развёрнут, сработал или покрывает конкретный путь.' },
];
const practice = revision({
slug: 'editorial-2027-08-practice-security-capstone', title: 'Большой разбор безопасности: актив, граница и договор о проверке', categories: ['Безопасность', 'Инженерные практики'], cover: '/assets/editorial/2027/security-capstone-2027-attack-path-contract.svg', excerpt: 'План на август 2027: как назвать актив и границу будущего вопроса, не создавая карту реальной системы.', readingMinutes: 26,
}, [
p('P114 — плановый материал на 2027-08 с редакционной датой и source cutoff 2026-07-31. Практическая ошибка начинается с невинной фразы «защитим важные данные»: команда уже выбрала слова, но ещё не назвала актив, его границу и то, что будет считаться допустимым свидетельством. Цена появляется сразу: следующий читатель дорисовывает систему, маршрут и решение по привычке, а затем обсуждает придуманное как обязательство.'),
p('Вторая цена — подмена договора о проверке самой проверкой. В будущем документе легко оставить название компонента, намёк на доступ или обещание «покрыть риск», хотя исходных материалов нет. Для P114 не существует настоящего актива, классификации, границы, маршрута, контроля, evidence, владельца или развертывания. Допустимый positive output здесь один: <code>synthetic-plan-hand-off</code>, причём <code>productionEffect</code> всегда <code>not-attempted</code>. Это не отчёт и не security-test; это форма, которая не даёт неизвестному стать правдоподобным фактом.'),
h2('Актив сначала называют, затем запрещают дополнять'),
p('Актив в этом сценарии — не объект в инфраструктуре и не запись в каталоге. Это synthetic label, нужный, чтобы у будущего разговора была тема. Метка <code>synthetic-asset-label</code> не содержит пользователя, файла, ключа, сервиса, класса данных или степени важности. За ней нельзя угадывать тип информации. Такое ограничение кажется бедным только пока не сравнишь его с альтернативой: любой реалистичный пример уже задаёт лишний контекст и заставляет выбирать защиту под несуществующий объект.'),
p('Граница отвечает на другой вопрос: что из разговора не попадает в документ. Её нельзя заменить существительным «приложение» или «платформа». В literal граница имеет значение <code>not-mapped</code>. Это не пустое поле, которое удобный reviewer заполнит позднее, а явный стоп для всех выводов о входе, выходе, хранении, передаче и доверии. Пока граница не принадлежит отдельному разрешённому scope, обсуждать «где защищать» преждевременно.'),
table('Минимальная карточка актива без ложной конкретики', ['Поле', 'Значение P114', 'Что это запрещает'], [
['Метка актива', '<code>synthetic-asset-label</code>', 'Выдать пример за существующий объект'],
['Классификация', '<code>not-declared</code>', 'Назвать данные, ценность или режим доступа'],
['Граница', '<code>not-mapped</code>', 'Рисовать вход, выход, хранение или доверенную зону'],
['Путь', '<code>not-mapped</code>', 'Считать порядок действий известным'],
['Допущения', '<code>not-accepted</code>', 'Сделать удобную догадку проектным решением'],
]),
h2('Почему «важный» не является характеристикой'),
p('Слово «важный» пытается склеить стоимость, доступ, целостность и ожидания пользователя в одну оценку. Для будущего сценария это опасная экономия: оно выглядит как классификация, но не содержит условия, кто его утвердил и на каких материалах. В P114 не нужно выбирать шкалу или приоритет. Нужно удержать разницу между вопросом «какой актив стоит обсуждать?» и утверждением «этот актив уже имеет такой-то класс». Второе требует предметного контекста, которого в плановом пакете нет.'),
p('NIST SP 800-53 Rev. 5 полезен здесь не каталогом готовых ответов. Публикация различает функциональную сторону контроля и assurance — степень уверенности в возможностях. Из этого следует узкий редакционный вывод: одна и та же картинка контроля не является доказательством, пока не определены объект и условия. Норма не сообщает, что P114 что-либо охраняет. Она помогает не назвать уверенностью один лишь рисунок.'),
p('NIST SSDF Version 1.1 также не превращает общий язык безопасной разработки в факт о конкретной команде. Он описывает практики верхнего уровня, которые можно встраивать в жизненный цикл, и общий язык для общения. Мы применяем именно эту часть: договор должен быть понятен следующему участнику. Мы не применяем публикацию как доказательство, что у пакета уже есть процесс, результат или снижение риска.'),
figure('/assets/editorial/2027/security-capstone-2027-attack-path-contract.svg', 'Схема синтетической карточки: метка актива, непоставленная граница и неотображённый путь ведут к договору о проверке; попытки объявить контроль или результат останавливаются.', 'Схема показывает редакционные статусы, а не путь атаки, систему, доступ или действие против неё.'),
h2('Договор о проверке — это перечень запретов на вывод'),
p('Полезный договор не обещает, что проверка обязательно состоится. Он фиксирует, какие вопросы должны быть решены в другом scope до появления любого evidence. Для актива это происхождение и допустимость данных. Для границы — что входит в рассмотрение и что исключено. Для пути — какие состояния можно описывать без реконструкции. Для контроля — какой смысл слова «покрывает» будет принят. Пока эти вопросы не получили авторизацию, каждая строка должна остаться именованной неизвестностью.'),
p('Отдельно важно не выдавать список источников за этот договор. Официальный документ объясняет термины и ограничения, но не получает разрешение на доступ и не выбирает метод. Поэтому URL и дата источника остаются в конце статьи, а fixed literal не содержит URL, target или параметров. Это разделение намеренное: источник делает лексику проверяемой, а scope делает возможным исследование. Одно не заменяет другое.'),
h2('Исполнимый пример не обращается к предмету'),
p('Пример можно запустить локально в Node. Он создаёт заранее объявленный literal, вызывает чистый evaluator и печатает только статус формы. Внутри нет чтения файлов, сети, environment, часов, secrets, telemetry, систем или данных. Нет импорта клиента и нет действия, похожего на security-test. Поэтому runnable code не способен узнать что-либо о чужом или собственном контуре.'),
code("import { inspectAssetBoundaryLiteral } from './upgrade-2027-08.mjs';\n\nconst card = inspectAssetBoundaryLiteral();\nconsole.log(card.asset, card.boundary, card.output);\n// synthetic-asset-label not-mapped synthetic-plan-hand-off"),
p('В этом выводе <code>synthetic-plan-hand-off</code> означает принятие строгой формы, не успех защитного действия. <code>not-mapped</code> не просит функцию найти путь по умолчанию. Если заменить literal произвольным объектом, evaluator остановится с <code>stop-unknown-fixed-literal</code>. Если взять известный отрицательный literal с заявленным путём, он также остановится. Такая проверка полезна ровно потому, что она не знает предметной системы.'),
h2('Три разных договора вместо одного слова «безопасность»'),
p('Первый договор относится к предмету: synthetic label нужен, чтобы следующая беседа не расползлась на весь продукт. Второй относится к границе: он перечисляет не компоненты, а запрет делать вид, что компоненты уже известны. Третий относится к evidence: он определяет, что публикация, схема и пример программы не считаются наблюдением. Эти договоры нельзя слить в одну строку «учтены требования безопасности». Такая строка не сообщает, где заканчивается её смысл и кто способен опровергнуть её.'),
p('Разделение помогает и при смене контекста. Допустим, будущий scope обнаружит, что вопрос вообще не про актив, а про правило обмена между двумя процессами. Первый договор можно заменить, не сохраняя неверную классификацию. Если окажется, что граница шире или уже, второй договор обновляется независимо от статьи. Если evidence собирать нельзя, третий договор честно завершит работу без результата. Monolithic «security checklist» обычно не даёт такой свободы: в нём изменение одного поля выглядит как отмена всего документа.'),
h2('Что считается попыткой незаметно расширить scope'),
p('Расширение редко приходит словом «расширение». Его видно по типу значения. Конкретное имя сервиса, поле payload, имя роли, дата будущего запуска, оценка критичности или обещание хранения — всё это утверждает, что автор уже получил материал и право им пользоваться. Даже без секрета такая деталь меняет читательскую модель. Поэтому evaluator сравнивает весь input только с заранее объявленными literals: он не нормализует похожий объект и не добавляет разумные defaults.'),
p('Строгое сравнение не является общим способом валидировать production данные. Оно выбрано для редакционной изоляции. Пакет не должен принимать «почти такую же» карточку, потому что именно в разнице может оказаться неразрешённый факт. Например, поле с реалистичной классификацией или состояние пути, похожее на известное, нельзя понизить до заметки. Возврат stop-status делает расхождение видимым до того, как оно будет переписано в prose или SVG.'),
p('У этого правила есть практический критерий: читатель должен суметь указать на каждое положительное существительное и найти для него либо fixed literal, либо будущий scope. Если такого адреса нет, существительное превращается в claim и подлежит удалению. Проверять стоит не только заголовки таблиц, но и подписи к рисункам, комментарии в примере и переходы между абзацами: именно там незаметно появляются «уже защищённый» объект, «известный» путь или «подходящий» контроль.'),
h2('Маршрут работы без имитации исследования'),
ol([
'Отметить документ как сценарий 2027-08 и повторить source cutoff 2026-07-31 рядом с вопросом.',
'Назвать только synthetic asset label; не добавлять тип данных, сервис, идентификатор, путь или классификацию.',
'Зафиксировать boundary и path как <code>not-mapped</code>, а допущения как <code>not-accepted</code>.',
'Сформулировать, какое evidence вообще могло бы быть допустимо в отдельном scope, не собирая и не имитируя его сейчас.',
'Прогнать fixed fixture: положительной остаётся лишь hand-off форма, любые claims должны получить stop-status.',
'Передать вопрос только в новый authorized scope, если будущему решению действительно понадобятся материалы.',
]),
h2('Ограничения этой практики'),
p('Карточка не является inventory, классификацией, model, assessment или разрешением на действие. Она не сообщает, существует ли объект, можно ли его наблюдать и нужен ли он следующему решению. Она не выбирает защитный механизм и не даёт критерия готовности. Это сознательное ограничение защищает текст от самого частого ложного вывода: раз есть аккуратная таблица, значит работа по безопасности уже началась.'),
p('У подхода есть цена: будущему участнику придётся повторно уточнить вопрос, а не получить готовую карту. Но эта цена меньше стоимости ошибочной конкретики. Именованная пустота легко расширяется в правильном контексте; выдуманная граница требует сначала объяснить, почему ей доверяли. Техлидская дисциплина здесь не в том, чтобы заполнить форму быстрее, а в том, чтобы не создавать технический долг из уверенных слов.'),
h2('Следующее действие'),
p('Следующее действие одно: если появится решение, которому нужны факты, открыть отдельный authorized scope с собственными входами, ограничениями и правилами хранения. Новый документ вправе определить актив и границу, но не вправе переписать P114 так, будто они были известны заранее. Пока такого scope нет, полезный и единственный положительный итог — <code>synthetic-plan-hand-off</code> с <code>productionEffect: not-attempted</code>.'),
], refs);
const mechanism = revision({
slug: 'editorial-2027-08-mechanism-security-capstone', title: 'Большой разбор безопасности: путь, покрытие и фальсифицируемость', categories: ['Безопасность', 'Архитектура'], cover: '/assets/editorial/2027/security-capstone-2027-control-evidence-matrix.svg', excerpt: 'План на август 2027: как удержать synthetic path и матрицу control coverage проверяемыми, не выдавая схему за результат.', readingMinutes: 27,
}, [
p('P114 рассматривает будущий август 2027 из точки 2026-07-31. Механическая ошибка появляется, когда путь угрозы рисуют настолько убедительно, что его начинают читать как установленную последовательность. У прямоугольников есть стрелки, у стрелок — названия, и мозг уже достраивает источник, переход и последствия. Цена — решение принимают по рисунку, хотя рисунок не содержит ни подтверждённого состояния, ни допустимого evidence.'),
p('Вторая цена скрыта в слове «покрытие». Оно звучит как завершённая работа: будто контроль уже выбран, проверен и относится к каждому участку пути. В P114 этого нет. Нет реального пути, control deployment, control evidence, security-test, finding, scan или положительного эффекта. Единственный неостанавливаемый output — <code>synthetic-plan-hand-off</code> с <code>productionEffect: not-attempted</code>. Механизм ниже учит отбрасывать недоказанные claims, а не превращать их в проектный статус.'),
h2('Путь — не история, а набор незаполненных связей'),
p('Синтетический путь начинается не с противника и не с технологии. Он начинается с вопроса о связях между пока неописанными состояниями. В fixed literal ему дано имя <code>synthetic-path-question</code>, а поле <code>states</code> равно <code>not-mapped</code>. Имя не кодирует endpoint, роль, способ доступа или переход. Оно существует только для того, чтобы evaluator мог отличить известную учебную форму от произвольного входа.'),
p('Такой дизайн противоречит привычке рисовать больше. В настоящей архитектурной схеме детали помогают заметить разрыв. В неавторизованном будущем сценарии те же детали становятся неподтверждёнными утверждениями. Поэтому P114 не строит threat model и не описывает attack technique. Он использует слово path только в значении «отношение, которое когда-нибудь может потребовать отдельного рассмотрения». Пока состояние не собрано, у path нет направления, начала, конца или вероятности.'),
table('Матрица фальсификации для synthetic path', ['Слой', 'Статус в P114', 'Что нельзя заключить'], [
['Связи пути', '<code>not-mapped</code>', 'Что последовательность существует или завершена'],
['Допущения', '<code>not-accepted</code>', 'Что удобное объяснение допустимо'],
['Покрытие контроля', '<code>not-selected</code>', 'Что мера относится к пути'],
['Свидетельство', '<code>not-collected</code>', 'Что механизм наблюдался или проверялся'],
['Эффект', '<code>not-attempted</code>', 'Что изменился production или риск'],
]),
h2('Покрытие — отношение, а не зелёная ячейка'),
p('Даже в реальной работе «control coverage» не должно означать декоративное соответствие названия контролю. Для такого вывода нужны как минимум объект, граница, цель, условия работы и способ отличить действие от отсутствия действия. В P114 ни один из этих элементов не определён. Поэтому значение <code>not-selected</code> честнее слова «частично»: частичность уже предполагает измеренную ось и выбранный объект сравнения.'),
p('NIST SP 800-53 Rev. 5 описывает каталог гибких контролей и отдельно говорит о функциональном и assurance-ракурсах. Для механизма P114 это источник ограничения: функция и уверенность не складываются автоматически в факт. Даже если будущий scope подберёт название из каталога, ему всё равно придётся показать, почему оно относится к его границе и какое свидетельство допустимо. Документ NIST не создаёт такую связь за него.'),
p('SSDF Version 1.1 вводит практики безопасной разработки как высокоуровневые и пригодные для интеграции в разные жизненные циклы. Мы не выводим из этого универсальный рецепт. Наоборот, высокая абстракция источника напоминает о границе применения: публикация может помочь задать вопрос к процессу, но не подтвердить, что конкретная мера была запущена, дала результат или закрыла неизвестный путь.'),
figure('/assets/editorial/2027/security-capstone-2027-control-evidence-matrix.svg', 'Матрица: synthetic path, невыбранное покрытие и несобранное evidence пересекаются в стоп-границе; только плановая передача вопроса остаётся разрешённым выходом.', 'Матрица не содержит реальных техник, целей, конфигураций или показателей. Она показывает, почему статус контроля нельзя получить из пустого пути.'),
h2('Фальсифицируемость начинается до положительного вывода'),
p('В этом материале фальсифицируемость не предлагает выполнять проверку. Она определяет, какие факты не позволят принять claim. Если путь не отображён, нельзя сказать, что что-то проходит по нему. Если control не выбран, нельзя назвать его покрытием. Если evidence не собран, нельзя выдать документ за подтверждение. Если deployment не attempted, нельзя говорить о production effect. Это четыре независимых стоп-условия; их нельзя компенсировать хорошим стилем схемы.'),
p('Такой порядок отличается от чеклиста best practices. Чеклист говорит, что полезно иметь. Фальсифицируемая карточка говорит, чего нельзя утверждать до появления определённого материала. Для техлида это практичнее: она позволяет остановить ложную эскалацию до спора о деталях. Пустая ячейка перестаёт быть неудобством и становится контрактом, который защищает следующего автора от давления «сделайте вывод уже сейчас».'),
h2('Исполнимый пример проверяет отказ от claim'),
p('Ниже нет схемы сети, входных адресов, payload, инструмента или вызова. Пример работает с неизменяемым literal в памяти и возвращает три строки, описывающие его ограничения. Модуль не читает file, environment, clock, secret, telemetry, system, data или network. Он не инициирует scan и не меняет состояние. Поэтому выполнение безопасно: оно проверяет только правило редакционной формы.'),
code("import { inspectCoverageLiteral } from './upgrade-2027-08.mjs';\n\nconst matrix = inspectCoverageLiteral();\nconsole.log(matrix.path, matrix.coverage, matrix.effect);\n// synthetic-path-question not-selected not-attempted"),
p('Строка <code>not-selected</code> не означает отказ от защиты и не предлагает оставить что-либо без решения. Она означает, что P114 не имеет права выбрать решение. А <code>not-attempted</code> относится только к production effect evaluator: никакое внешнее действие не выполнялось. Если вызвать assessor с known negative literal, где coverage объявлено claimed, результат станет <code>stop-claimed-control-or-evidence</code>, а не предупреждением, которое можно проигнорировать.'),
h2('Четыре оси не становятся доказательством друг друга'),
p('У path, control, evidence и effect разные владельцы смысла. Path отвечает на вопрос о связи состояний. Control — о намерении выбрать некоторую функцию. Evidence — о происхождении наблюдения или подтверждения. Effect — о том, что изменилось за пределами чистой функции. Даже в будущем проекте нельзя переносить значение по диагонали: выбранный control не доказывает path, существующее evidence не доказывает effect, а желаемый effect не делает coverage достаточным.'),
p('В синтетическом сценарии все четыре оси остаются отрицательными status. Это не попытка создать математическую модель риска из строк. Наоборот, строки не имеют числового веса, вероятности или приоритета. Их задача скромнее: зафиксировать, что положительное утверждение на любой оси нуждается в собственном основании. Такой дизайн не позволит красиво ранжировать варианты, зато не позволит получить рейтинг из отсутствующих данных.'),
h2('Как читать stop-status'),
p('Stop-status не называет ошибку безопасности и не говорит, что input опасен. Он говорит только, что input нарушает редакционный контракт P114. Например, <code>stop-claimed-asset-or-path</code> не обнаруживает маршрут и не запрещает настоящую карту в другом месте. Он отвергает попытку представить карту внутри этого планового literal. В результате текст остаётся точным: мы проверили не систему, а то, что статья не стала выдавать план за evidence.'),
p('Это различие полезно для review. Reviewer не обязан спорить, правдоподобна ли заявленная деталь; ему достаточно увидеть, что деталь отсутствует в разрешённой форме. Спор о правдоподобии обычно тянет за собой новые предположения и заставляет автора защищать неподтверждённый контекст. Стоп переносит разговор в продуктивное место: либо убрать claim, либо открыть отдельный scope, где будут правила для фактов.'),
h2('Матрица должна сохранять асимметрию доказательства'),
p('У положительного claim и у отрицательного статуса разная цена. Чтобы сказать «покрытие есть», будущему scope потребуется связь нескольких материалов. Чтобы сказать «покрытие здесь не выбрано», достаточно не делать выбора в этом literal. Эту асимметрию нельзя сгладить словом «неполное»: оно создаёт впечатление, будто шкала уже существует, а осталась лишь работа по заполнению. В P114 нет шкалы, процента, ранга и критерия достаточности.'),
p('То же относится к связке source и evidence. Датированный официальный PDF надёжно сообщает, что опубликованный текст существовал в указанной версии. Он не сообщает, что synthetic path имеет свойства, к которым можно применить норму. Поэтому матрица намеренно хранит references вне evaluator input: evaluator проверяет форму плановой карточки, а статья объясняет язык этой формы. Если смешать эти уровни, ссылка станет ложным substitute для отсутствующего основания.'),
h2('Как собрать вопрос, который можно опровергнуть'),
ol([
'Описать один synthetic path label без адресов, ролей, способов доступа, последовательности и утверждений о прошлом.',
'Отделить mapping path от выбора control: первое остаётся <code>not-mapped</code>, второе — <code>not-selected</code>.',
'Записать evidence как <code>not-collected</code>; не заменять его ссылкой на стандарт или общим ожиданием.',
'Для каждого будущего claim заранее назвать необходимое условие: объект, границу, method и допустимое свидетельство.',
'Проверить evaluator отрицательными literals: claimed path, claimed coverage и claimed evidence обязаны остановиться.',
'Передавать только вопрос о следующем scope, а не оценку, что он должен доказать.',
]),
h2('Граница между схемой и решением'),
p('Схема полезна, когда сокращает число неверных интерпретаций. Она становится опасной, когда её компактность скрывает отсутствие данных. В P114 стрелка на SVG означает связь статусов, а не перемещение данных, привилегий или команд. Цвет stop не обозначает блокирование действия в реальной системе; это правило evaluator. Такая подпись важнее художественной точности, потому что читатель не должен переносить учебную метафору в production.'),
p('Механизм также не создаёт модель рисков, не сравнивает варианты и не выдаёт рекомендацию по контролям. Он не может оценить достаточность, потому что у него нет шкалы, входов и target. Статья не прячет эти отсутствия под словом «coverage». Если будущий scope выберет другой язык или отменит вопрос, P114 не станет ошибочным: он изначально передаёт не ответ, а условия честного ответа.'),
h2('Следующее действие'),
p('Следующее действие ограничено новым authorized scope, если конкретному решению потребуется evidence. В нём отдельно определяются допустимые объект, граница, метод, хранение материалов и правило отрицательного результата. Его результат не добавляется задним числом в P114. До этого механический итог один: strict evaluator возвращает <code>synthetic-plan-hand-off</code> и <code>productionEffect: not-attempted</code>; все положительные claims fail-closed.'),
], refs);
const field = revision({
slug: 'editorial-2027-08-field-security-capstone', title: 'Большой разбор безопасности: evidence hand-off без вымышленной проверки', categories: ['Безопасность', 'Практика команд'], cover: '/assets/editorial/2027/security-capstone-2027-review-handoff-loop.svg', excerpt: 'План на август 2027: как передать вопрос о безопасности без истории об incident, тесте или закрытом пути.', readingMinutes: 25,
}, [
p('P114 — сценарий на август 2027, составленный 2026-07-31. В полевом hand-off самая дорогая ошибка — отдать следующему человеку документ, который выглядит как итог: в нём есть заголовок, схема, слова о контроле и аккуратный список следующей работы. Получатель естественно предполагает, что кто-то уже видел предмет и проверил границы. Цена — будущая работа стартует с ложной памяти, а не с честного вопроса.'),
p('Вторая цена — перепутать передачу evidence с поручением провести безопасность. У P114 нет incident, реального security-test, scan, finding, advisory, attack, exploit, ticket, owner, production effect или закрытого path. Нет и положительной ветки кроме <code>synthetic-plan-hand-off</code> с <code>productionEffect: not-attempted</code>. Полевая статья не сообщает, что проверить; она показывает, как не сделать отсутствие материалов основанием для уверенного narrative.'),
h2('Hand-off передаёт границу незнания'),
p('Пакет пригоден к передаче, когда его можно прочитать без домысливания. В P114 названы только дата редактора, плановый месяц, source cutoff и три synthetic labels. Их достаточно, чтобы определить временную и смысловую рамку. Asset classification остаётся <code>not-declared</code>, boundary и path — <code>not-mapped</code>, coverage — <code>not-selected</code>, evidence — <code>not-collected</code>. Эти значения не просят следующего человека «дозаполнить форму»; они запрещают считать поля заполненными.'),
p('Особое место занимает адресат. В живой передаче хочется поставить owner, команду и дедлайн. Здесь это было бы фиктивным организационным действием. Literal хранит <code>recipient: not-assigned</code> и <code>route: not-opened</code>. Это не дефект управления, а защита от тайного назначения: до нового scope не существует согласованной роли, очереди или обещания выполнить проверку. Можно передать сам вопрос, но нельзя передать чужую обязанность.'),
table('Состав hand-off без выдуманного нарратива', ['Часть', 'Что передаётся', 'Чего нет в P114'], [
['Время', 'editorial date, planning issue, source cutoff', 'История события или будущий результат'],
['Предмет', 'только synthetic labels', 'Система, данные, target или закрытая граница'],
['Статусы', 'not-declared / not-mapped / not-selected', 'Оценка важности или coverage'],
['Evidence', '<code>not-collected</code>', 'Отчёт, finding, scan или test output'],
['Адресация', '<code>not-assigned</code>', 'Owner, ticket, очередь или обязательство'],
]),
h2('Почему красивый narrative ломает evidence'),
p('Narrative начинает вредить не тогда, когда он явно выдуман. Обычно он появляется как связка: «поэтому нужен контроль», «после проверки стало понятно», «дальше владелец закроет». В каждой такой фразе спрятаны объект, метод, наблюдение и полномочие. Если их нет в record, история лишает будущего reviewer возможности отказаться от исходной постановки. Он уже должен спорить не с вопросом, а с ощущением, что команда что-то обещала.'),
p('Полевой формат должен сохранять возможность отрицательного ответа. Новый scope может решить, что предмет не входит в его границы, что evidence нельзя собирать или что вопрос не влияет на решение. Это не провал hand-off. Напротив, это его проверка: корректная передача не подталкивает получателя к заранее заданному выводу. P114 не содержит скрытой метрики успеха и не использует «закрыть» как эвфемизм для будущего положительного результата.'),
figure('/assets/editorial/2027/security-capstone-2027-review-handoff-loop.svg', 'Петля передачи: fixed literal проходит проверку времени, отсутствия claims и отсутствия назначения; при любом claim он останавливается, иначе передаётся только синтетический плановый вопрос.', 'Петля не описывает incident response или security review. Она визуализирует редакционное fail-closed правило передачи без owner и evidence.'),
h2('Источники остаются словарём, не приложением к делу'),
p('NIST SSDF Version 1.1 уместен в hand-off как официальный общий словарь для коммуникации вокруг безопасной разработки. Но его текст не сообщает, что участники P114 существуют, договорились или выполнили практику. Это важно написать в самом артефакте: ссылка не становится evidence, даже если она ведёт на нормативную публикацию и точно датирована.'),
p('NIST SP 800-53 Rev. 5 полезен для различения контроля и уверенности в нём. В field-материале это помогает не превратить колонку «control» в колонку «подтверждено». При этом каталог предназначен для управления риском в организационном процессе; он не назначает здесь метод, адресата или срок. Мы используем публикацию как границу интерпретации, не как разрешение на любые действия.'),
h2('Runnable hand-off остаётся в памяти'),
p('Пример ниже не создаёт файл, не читает окружение, не получает время, не обращается к сети и не вызывает инструмент безопасности. Он получает fixed literal, возвращает только fields hand-off и печатает их. В данных нет секрета, target, реального имени, конфигурации или телеметрии. Поэтому запуск демонстрирует fail-closed интерфейс, а не фиксирует состояние чего-либо вне процесса Node.'),
code("import { inspectSecurityHandoffLiteral } from './upgrade-2027-08.mjs';\n\nconst handoff = inspectSecurityHandoffLiteral();\nconsole.log(handoff.status, handoff.recipient);\n// synthetic-plan-hand-off not-assigned"),
p('Здесь <code>not-assigned</code> не предлагает анонимно выполнить работу. Он делает невозможной подмену «следующего шага» реальным назначением. Единственное следующее действие строкой evaluator — открыть отдельный authorized scope, только если решение требует evidence. При неизвестном объекте assessor возвращает stop. При known literal с claimed evidence — тоже stop. Позитивный статус не повышается до compliance, protection или production effect.'),
h2('Получатель должен получить право отказаться'),
p('Передача без права отказаться — это скрытая эскалация. В ней вопрос уже сформулирован как обязательная работа, а отсутствие owner маскируется словами «следующая команда разберётся». P114 действует иначе: recipient не назначен, route не открыт, а next action условен. Будущий участник может принять решение об отдельном scope, сузить вопрос или признать, что evidence не нужно для доступного выбора. Все три исхода совместимы с корректной передачей.'),
p('Эта свобода не отменяет ответственность автора. Автор отвечает за то, чтобы не добавить деталей, которые делают отказ психологически невозможным: мнимой срочности, конкретного target, обещанной выгоды или намёка на уже выявленную проблему. Чем точнее он удерживает исходную неизвестность, тем легче следующему человеку оценить цену будущего исследования. В field-формате ценность не в количестве заполненных полей, а в отсутствии долга, созданного чужими выводами.'),
h2('Как сохранить provenance пустого поля'),
p('Пустое по смыслу поле тоже имеет происхождение. <code>not-collected</code> означает не «данные были и потерялись», а «этот пакет их не получает и не имитирует». <code>not-selected</code> не означает отказ существующей команды, а отсутствие выбора в literal. <code>not-assigned</code> не означает скрытого владельца. Эти различия должны пережить копирование карточки: иначе следующий редактор сделает из отрицательного статуса пробел для собственной догадки.'),
p('Поэтому hand-off не прикладывает образцы журналов, снимки экрана, фрагменты конфигурации и правдоподобные команды. Даже обезличенная деталь способна стать якорем: читатель начнёт считать её representative и подгонит будущую работу под этот образец. В P114 единственный безопасный artefact — fixed literal и его evaluator output. Они имеют ясное происхождение внутри модуля и не претендуют на происхождение вне него.'),
h2('Передача не должна менять класс документа'),
p('Самый незаметный сбой hand-off происходит при редактуре. Автор начинает с карточки вопроса, а затем добавляет в письмо краткое объяснение «почему это важно». Если объяснение содержит предполагаемый ход событий, оно меняет класс всего пакета: у получателя появляется narrative, которого нет в literal. Поэтому P114 требует проверять не только JSON-подобную форму, но и prose вокруг неё. Текст должен повторять границу, а не обогащать её удобными деталями.'),
p('Это относится и к визуалу. Диаграмма может быть доступной, аккуратной и всё равно чрезмерно конкретной. Иконка замка, сервер с подписью или стрелка между известными слоями быстро превращают план в якобы наблюдённую архитектуру. В наших SVG названы статусы и проверки, а не участники системы. Русские alt и подписи прямо говорят, что изображение показывает правило редакционного маршрута. Такая избыточная явность нужна для мобильного чтения, где подпись часто читают раньше основного объяснения.'),
h2('Что получатель может изменить, не ломая P114'),
p('Получатель может отказаться от вопроса, запросить новый scope или определить, что для решения достаточно других материалов. Он не может изменить старый literal добавлением своего target, evidence или owner, а затем сослаться на P114 как на первичную запись. Новая конкретика должна жить в новом artefact со своей датой и правилами. Это различие не формальность: оно сохраняет цепочку происхождения. Через месяц нельзя будет перепутать будущий вывод с тем, что было известно на редакционную дату.'),
p('Если будущий scope всё же появляется, его первая проверка не обязана быть технической. Он может остановиться на проверке полномочий, данных или необходимости. P114 заранее оставляет такой выход корректным. Ему не нужен искусственный положительный результат, чтобы оправдать существование серии. Техлид получает более полезный сигнал: вопрос дошёл до нужной границы и не начал потреблять доступ, время или доверие без явного решения.'),
h2('Порядок передачи без поручения и без теста'),
ol([
'Проверить три даты и слово scenario: P114 относится к плану 2027-08, а не к прошедшему событию.',
'Оставить asset, boundary и path в named fixed статусах; убрать реалистичные имена, маршруты и детали доступа.',
'Отметить control coverage как <code>not-selected</code>, evidence как <code>not-collected</code>, deployment как <code>not-attempted</code>.',
'Не добавлять owner, ticket, очередь, дедлайн или обязательство: recipient должен остаться <code>not-assigned</code>.',
'Запустить fixture и убедиться, что claimed path, control, evidence и effect не проходят как предупреждения.',
'Открывать новый scope только при решении, которому необходимы материалы; иначе сохранить hand-off без развития narrative.',
]),
h2('Ограничения полевой формы'),
p('Этот hand-off не является review, audit, assessment, test plan или доказательством соответствия. Он не говорит, что в системе есть проблема или что проблема отсутствует. Он не определяет источник данных, способ доступа, хранение artefacts, ответственность или критерий завершения. Если читателю нужен один из этих элементов, он должен признать, что просит работу за пределами P114, а не искать его между строк.'),
p('Ограничение особенно полезно для безопасности, где аккуратный документ легко воспринимается как результат. Мы оставляем дизайн немного неудобным: он не показывает эффект, не называет победителя и не даёт команды. Зато он не переносит последствия воображаемой проверки на будущую команду. Это и есть минимальная цена честного evidence hand-off — оставить неизвестность видимой до того, как она получит законное происхождение.'),
h2('Следующее действие'),
p('Следующее действие не меняется: новый authorized scope можно открыть только при конкретной потребности в evidence. Он обязан задать собственные временные границы, разрешённые inputs, метод, владельца и способ завершиться без положительного вывода. Его факты останутся в новом artefact, а P114 сохранит роль исходного вопроса. До этого единственный допустимый output — <code>synthetic-plan-hand-off</code> с <code>productionEffect: not-attempted</code>.'),
], refs);
export const revisions = deepFreeze([practice, mechanism, field]);
export function verifyRevisionsAgainstFixture() {
const fixture = runSecurityCapstoneFixture();
const articleChecks = revisions.map((item) => {
const text = bodyText(item.contentHtml);
return text.length >= 10000 && text.length <= 13000 && /(цен[аы]|стоимост|дорог|потер)/i.test(text.slice(0, 1300)) && /<table>/.test(item.contentHtml) && /<figure>/.test(item.contentHtml) && /<pre><code>/.test(item.contentHtml) && /<ol>/.test(item.contentHtml) && /2027-08/.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');