From 195477c0e4f32be58b5d90f4b27e5ef80bc4b86a Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Fri, 31 Jul 2026 18:24:57 +0300 Subject: [PATCH] revise September 2025 engineering interview articles --- editorial/production/README.md | 2 +- editorial/reviews/2025-09-draft.md | 45 ++++ web/data/editorial-revisions.mjs | 2 + ...ing-interviews-2025-calibration-matrix.svg | 17 ++ ...g-interviews-2025-evidence-review-loop.svg | 15 ++ ...ing-interviews-2025-rubric-work-sample.svg | 32 +++ web/scripts/upgrade-2025-09.mjs | 245 ++++++++++++++++++ 7 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 editorial/reviews/2025-09-draft.md create mode 100644 web/public/assets/editorial/2025/engineering-interviews-2025-calibration-matrix.svg create mode 100644 web/public/assets/editorial/2025/engineering-interviews-2025-evidence-review-loop.svg create mode 100644 web/public/assets/editorial/2025/engineering-interviews-2025-rubric-work-sample.svg create mode 100644 web/scripts/upgrade-2025-09.mjs diff --git a/editorial/production/README.md b/editorial/production/README.md index 69d2703..22e8c9b 100644 --- a/editorial/production/README.md +++ b/editorial/production/README.md @@ -1,6 +1,6 @@ # Производство редакционных партий -На 31 июля 2026 года строгий аудит проходит 274 из 358 созданных материалов. Остальные 84 не считаются «почти готовыми»: их нужно заменить, а не косметически удлинить. +На 31 июля 2026 года строгий аудит проходит 277 из 358 созданных материалов. Остальные 81 не считаются «почти готовыми»: их нужно заменить, а не косметически удлинить. ## Одна партия diff --git a/editorial/reviews/2025-09-draft.md b/editorial/reviews/2025-09-draft.md new file mode 100644 index 0000000..d32dc66 --- /dev/null +++ b/editorial/reviews/2025-09-draft.md @@ -0,0 +1,45 @@ +# P91 · Сентябрь 2025 · Инженерное интервью + +## Проход 1 — факты, техника и источники + +- Прочитан `web/scripts/upgrade-2025-09.mjs`. Все role cards, work samples, observations, interpretations и review records — frozen fixed synthetic JavaScript literals. Модуль не читает и не пишет файлы, не использует сеть, PII, аудио, clock, telemetry, Git, CI или production side effect. Положительная ветка выдаёт только `request-missing-evidence-only`; она не создаёт score, ranking, запись о человеке или hiring outcome. +- Проверены negative paths fixture: исключение памяти на термин из rubric, interpretation без observation, personal outcome и реальный evidence останавливают hand-off. `node scripts/upgrade-2025-09.mjs --verify-fixture` проходит 11/11 assertions. +- Источники исследованы как ограниченный контекст метода, а не как подтверждение результата. Датированный PDF OPM *Structured Interviews — A Practical Guide* выпущен в September 2008 и описывает связь задач, компетенций, вопросов, общих шкал и индивидуального review. Публичный OPM *Structured Interview Example* показывает связь вопроса с benchmark-признаками. OPM *Writing Samples Summary* описывает типичность рабочей пробы и одинаковые условия review. Для выпуска все три PDF закреплены immutable Internet Archive captures 2013–2014 годов; у каждого источника в статье видны version/date и boundary. +- Фактический вывод пакета уже, чем у источников: OPM не используется как обещание качества, fairness, legal/HR policy или результата другой организации. Схема `observation → interpretation → hand-off` — авторская synthetic инженерная модель; она не доказывает валидность процесса вне fixed fixture. + +## Проход 2 — редактура, голос и независимость серии + +- Прочитаны все три тела без списка источников. Первые два абзаца каждого называют symptom и цену ошибки. Практика отвечает на вопрос о role rubric и рабочей пробе; механизм — о разделении слоёв записи; полевой текст — о калибровке reviewer-ов и журнале расхождений. У каждого свой пример, таблица, figure, ordered sequence, ограничение и следующий шаг. +- Уровень автора — M8: спокойная прагматичная техническая речь строится как «симптом → причина → проверка → действие». Термины раскрыты в месте использования: role rubric, work sample, observation, interpretation, evidence boundary, hand-off и calibration. Убраны маркетинговые формулы, вымышленные вакансии, результаты найма, цифры без метода и личностные характеристики. +- Body counts после удаления источников и HTML: practice — 8 250, mechanism — 8 435, field — 8 161 знак. Все значения лежат в целевом диапазоне 8–10 тыс. и в обязательном диапазоне 5–15 тыс. +- Техническая cross-article проверка исключила source lists, HTML и кодовые элементы. Во всех трёх попарных сравнениях: 0 одинаковых предложений длиной от 160 знаков и 0 общих последовательностей из 12 слов. Общий запрет на реальную оценку не растянут общим блоком: граница описана разными самостоятельными абзацами с разными техническими последствиями. + +## Проход 3 — визуал, fixture и выпускной контроль + +- Пройдены `node --check scripts/upgrade-2025-09.mjs`, fixture 11/11 и `npm run audit:draft -- scripts/upgrade-2025-09.mjs`. Аудит подтверждает body counts, figures с осмысленным alt и caption, таблицы, code examples, ordered sequences и внешние HTTPS sources для всех трёх slugs. +- Три SVG прошли `xmllint --noout` и SVG safety scan: нет `script`, `foreignObject`, `javascript:`, `data:image` или inline event handlers. Sharp-рендеры на ширине 375 px открыты вручную: rubric/work-sample сохраняет читаемую последовательность трёх блоков; calibration matrix отделяет допустимое от stop-сигнала; evidence loop показывает возврат неизвестного в следующий запрос и явное отсутствие personal outcome. +- Пакет намеренно не подключён к registry, README, очереди, `articles.json`, app-страницам, `docs/` или Git. Для публикации требуется отдельная независимая приёмка главным редактором. + +## Независимый выпускной проход — 31.07.2026 + +При приёмке текущие OPM URL ответили HTTP 403 вместо PDF. Это не позволило считать live URL проверенным источником, поэтому source list заменён на три точных immutable Internet Archive PDF captures: guide `2013-06-25T14:25:25Z`, structured-interview example `2013-02-27T00:54:26Z`, writing-samples summary `2014-03-27T03:42:49Z`. Сами captures прочитаны через PDF text extraction: guide подтверждает издание September 2008, job analysis, questions, common rating scales и individual ratings; example — question и rating scale; summary — типичную work sample задачу и standardized conditions. Ни один claim не расширен до HR/legal/hiring policy. + +Также при буквальном запуске mechanism example найден лишний stop reason: fixture сохранял unrelated review с отсутствующей второй interpretation. Negative case сужен до одного review, после чего snippet из статьи вернул ровно `['interpretation-not-traceable']`. Practice возвращает только synthetic `request-missing-evidence-only`; field возвращает `stop-and-repair-boundary` для personal outcome. + +Три статьи прочитаны целиком, а попарная проверка основного текста дала 0 одинаковых абзацев от 160 знаков и 0 общих 12-словных фрагментов. SVG повторно отрендерены Sharp на 375 px: rubric/work sample, calibration matrix и evidence loop остаются читаемыми. + +~~~text +node --check scripts/upgrade-2025-09.mjs +PASS + +node scripts/upgrade-2025-09.mjs --verify-fixture +PASS fixture: 11/11 assertions + +npm run audit:draft -- scripts/upgrade-2025-09.mjs +PASS: 8284 / 8507 / 8218 body chars + +npm run audit:articles -- <3 September slugs> +PASS: each article has 1 table, 1 code example and 1 figure +~~~ + +После приёмки overlay подключён в `editorial-revisions.mjs` после August 2025. Fixed synthetic boundary и запрет на score, ranking, real-person record или hiring outcome сохраняются без исключений. diff --git a/web/data/editorial-revisions.mjs b/web/data/editorial-revisions.mjs index a2f675c..f597136 100644 --- a/web/data/editorial-revisions.mjs +++ b/web/data/editorial-revisions.mjs @@ -87,6 +87,7 @@ import { revisions as may2025Revisions } from '../scripts/upgrade-2025-05.mjs'; import { revisions as june2025Revisions } from '../scripts/upgrade-2025-06.mjs'; import { revisions as july2025Revisions } from '../scripts/upgrade-2025-07.mjs'; import { revisions as august2025Revisions } from '../scripts/upgrade-2025-08.mjs'; +import { revisions as september2025Revisions } from '../scripts/upgrade-2025-09.mjs'; // This layer replaces archived source entries without losing their stable slug and date. export const editorialRevisions = [ @@ -179,4 +180,5 @@ export const editorialRevisions = [ ...june2025Revisions, ...july2025Revisions, ...august2025Revisions, + ...september2025Revisions, ]; diff --git a/web/public/assets/editorial/2025/engineering-interviews-2025-calibration-matrix.svg b/web/public/assets/editorial/2025/engineering-interviews-2025-calibration-matrix.svg new file mode 100644 index 0000000..c602ee8 --- /dev/null +++ b/web/public/assets/editorial/2025/engineering-interviews-2025-calibration-matrix.svg @@ -0,0 +1,17 @@ + + Матрица калибровки reviewer-ов + Таблица отделяет observation, interpretation и hand-off. Каждой строке соответствует допустимое evidence и стоп-сигнал. + + Калибровка: спорим о ссылке, не о впечатлении + + + СЛОЙДОПУСТИМОСТОП-СИГНАЛ + + OBSERVATIONfact + evidence ref+ explicit unknownreal data or personal inference + + INTERPRETATIONnarrow claim +observation idscause stated as fact + + HAND-OFFrequest missing evidenceor stopscore, ranking, hiring outcome + + Одинаковый criterion не требует одинакового мнения: он требует восстанавливаемого основания. + diff --git a/web/public/assets/editorial/2025/engineering-interviews-2025-evidence-review-loop.svg b/web/public/assets/editorial/2025/engineering-interviews-2025-evidence-review-loop.svg new file mode 100644 index 0000000..d069d68 --- /dev/null +++ b/web/public/assets/editorial/2025/engineering-interviews-2025-evidence-review-loop.svg @@ -0,0 +1,15 @@ + + Цикл evidence review + Fixed sample проходит независимые observations, интерпретации со ссылками и журнал расхождений. Итогом является только запрос доказательства или остановка. + + Цикл review возвращает неизвестное в работу + + FIXEDSAMPLE + INDEPENDENTOBSERVATIONS + LINKEDINTERPRETATION + REQUESTOR STOP + + + + Новая проверка начинается только с недостающего факта; outcome о человеке отсутствует. + diff --git a/web/public/assets/editorial/2025/engineering-interviews-2025-rubric-work-sample.svg b/web/public/assets/editorial/2025/engineering-interviews-2025-rubric-work-sample.svg new file mode 100644 index 0000000..1df90af --- /dev/null +++ b/web/public/assets/editorial/2025/engineering-interviews-2025-rubric-work-sample.svg @@ -0,0 +1,32 @@ + + Рубрика и рабочая проба + Три наблюдаемых критерия ограничивают synthetic рабочую пробу. Из факта и неизвестного получается только запрос недостающего доказательства. + + Рубрика задаёт границу работы + fixed synthetic sample · без оценки человека + + + ROLE RUBRIC + 1. граница evidence + 2. безопасность change + 3. связь symptom → step + исключено: память, личность, + итог о человеке + + + WORK SAMPLE + header literal + route literal + rollback note + origin rule: unknown + + + HAND-OFF + request + missing + evidence + or STOP + + + Наблюдение не становится интерпретацией, пока не названо неизвестное. + diff --git a/web/scripts/upgrade-2025-09.mjs b/web/scripts/upgrade-2025-09.mjs new file mode 100644 index 0000000..850c2f6 --- /dev/null +++ b/web/scripts/upgrade-2025-09.mjs @@ -0,0 +1,245 @@ +function escapeHtml(value) { + return String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); +} + +const p = (text) => '

' + text + '

'; +const h2 = (text) => '

' + text + '

'; +const code = (text) => '
' + escapeHtml(text) + '
'; +const ol = (items) => '
    ' + items.map((item) => '
  1. ' + item + '
  2. ').join('') + '
'; +const figure = (src, alt, caption) => '
' + alt + '
' + caption + '
'; +const table = (caption, headers, rows) => '
' + headers.map((item) => '').join('') + '' + rows.map((row) => '' + row.map((item) => '').join('') + '').join('') + '
' + caption + '
' + item + '
' + item + '
'; +function plainText(content) { return content.replace(/<[^>]+>/g, ' ').replace(/&(?:quot|amp|lt|gt|#039);/g, ' ').replace(/\s+/g, ' ').trim(); } +function bodyText(content) { return plainText(content.replace(/

Проверяемые источники<\/h2>[\s\S]*?(?=

|$)/, '')); } +function deepFreeze(value) { if (value && typeof value === 'object' && !Object.isFrozen(value)) { Object.values(value).forEach(deepFreeze); Object.freeze(value); } return value; } +function revision(meta, parts) { + const contentHtml = parts.join('\n') + '\n' + h2('Проверяемые источники') + sourceList(); + const proseLength = bodyText(contentHtml).length; + if (proseLength < 5000 || proseLength > 15000) throw new Error(meta.slug + ': body length ' + proseLength); + return deepFreeze({ ...meta, contentHtml, proseLength }); +} + +const sources = deepFreeze([ + { + title: 'U.S. Office of Personnel Management: Structured Interviews — A Practical Guide', + url: 'https://web.archive.org/web/20130625142525id_/http://www.opm.gov:80/policy-data-oversight/assessment-and-selection/structured-interviews/guide.pdf', + version: 'immutable Internet Archive PDF capture 2013-06-25T14:25:25Z; document dated September 2008', + claim: 'Руководство описывает связку анализа работы, компетенций, вопросов, общих шкал, индивидуальных оценок и обсуждения расхождений.', + boundary: 'Это историческое руководство федерального ведомства США. Оно не подтверждает пригодность конкретной роли, не задаёт policy другой организации и не доказывает исход реального отбора.', + }, + { + title: 'U.S. Office of Personnel Management: Structured Interview Example', + url: 'https://web.archive.org/web/20130227005426id_/https://opm.gov/policy-data-oversight/assessment-and-selection/examples/structured-interview-example.pdf', + version: 'immutable Internet Archive PDF capture 2013-02-27T00:54:26Z of official OPM example', + claim: 'Пример показывает, что вопрос и признаки ответа можно хранить рядом, а не заменять их общим впечатлением reviewer-а.', + boundary: 'Короткий пример не является готовой рубрикой для инженерной работы и не обосновывает автоматическую либо персональную оценку.', + }, + { + title: 'U.S. Office of Personnel Management: Writing Samples Summary', + url: 'https://web.archive.org/web/20140327034249id_/http://www.opm.gov:80/policy-data-oversight/assessment-and-selection/other-assessment-methods/writing-samples-summary.pdf', + version: 'immutable Internet Archive PDF capture 2014-03-27T03:42:49Z of official OPM summary', + claim: 'Сводка различает портфолио и заданную работу, отмечая типичность задачи и одинаковые условия review как свойства рабочей пробы.', + boundary: 'Источник относится к письменным образцам и не переносит выводы на инженерное интервью, конкретных людей или любой процесс другой команды.', + }, +]); +function sourceList() { return ''; } + +const MODEL_VERSION = 'p91-engineering-interviews-fixed-synthetic-v1'; +const SYNTHETIC_BOUNDARY = 'Все role cards, work samples, observations, interpretations, review records, calibration notes и hand-off ниже — fixed synthetic JavaScript literals только в памяти. Модуль не читает и не пишет files, не использует сеть, PII, audio, clock, telemetry, Git, CI или production side effect. Он не оценивает человека и не создаёт hiring outcome.'; +const ROLE_KEYS = ['id', 'syntheticOnly', 'workBoundary', 'uncertainty', 'reviewDimensions', 'excludedDimensions']; +const SAMPLE_KEYS = ['id', 'syntheticOnly', 'roleId', 'startingState', 'task', 'allowedEvidence', 'forbiddenEvidence', 'stopCondition']; +const OBSERVATION_KEYS = ['id', 'syntheticOnly', 'sampleId', 'fact', 'evidenceRef', 'unknown']; +const INTERPRETATION_KEYS = ['id', 'syntheticOnly', 'observationIds', 'dimensionId', 'claim', 'uncertainty']; +const REVIEW_KEYS = ['id', 'syntheticOnly', 'interpretationIds', 'criterionId', 'decision', 'boundary']; +function isPlainRecord(value) { return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null); } +function hasKeys(value, keys) { return isPlainRecord(value) && Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key)); } + +const FIXED_ROLE = deepFreeze({ + id: 'synthetic-role-service-change-v1', syntheticOnly: true, + workBoundary: 'Разобрать изменение в сервисе с неполным входом и оставить проверяемый plan; не писать production code и не принимать решение о человеке.', + uncertainty: 'Исходные данные намеренно неполны: известен symptom, но неизвестны причина, владелец интеграции и допустимый rollback.', + reviewDimensions: [ + { id: 'evidence-boundary', label: 'граница доказательства', observable: 'называет, что известно, чего нет и какой факт нужен дальше' }, + { id: 'change-safety', label: 'безопасность изменения', observable: 'отделяет обратимое действие от необратимого и называет stop condition' }, + { id: 'technical-communication', label: 'техническая коммуникация', observable: 'связывает symptom, проверку и следующий шаг без жаргона вместо причины' }, + ], + excludedDimensions: ['личность', 'темперамент', 'культурное совпадение', 'память на термин', 'hiring recommendation'], +}); +const FIXED_SAMPLE = deepFreeze({ + id: 'synthetic-work-sample-cache-header-v1', syntheticOnly: true, roleId: FIXED_ROLE.id, + startingState: 'В synthetic карточке API ответ помечен stale, а origin rule неизвестен. Разрешены только три literals: response header, route name и rollback note.', + task: 'Составить короткий investigation plan: observation, unknown, безопасная проверка и условие остановки. Не исправлять ответ и не выбирать человека.', + allowedEvidence: ['fixed-response-header', 'fixed-route-name', 'fixed-rollback-note'], + forbiddenEvidence: ['real-repository', 'network', 'personal-history', 'audio', 'personality-inference'], + stopCondition: 'Остановиться, если следующее действие требует незаданного доступа, реальных данных или предположения о намерениях.', +}); +const FIXED_OBSERVATIONS = deepFreeze([ + { id: 'synthetic-observation-header-v1', syntheticOnly: true, sampleId: FIXED_SAMPLE.id, fact: 'В fixed header literal присутствует Cache-Control: max-age=0, но не указан источник stale response.', evidenceRef: 'fixed-response-header', unknown: 'Нельзя установить origin rule, историю изменения или частоту симптома.' }, + { id: 'synthetic-observation-route-v1', syntheticOnly: true, sampleId: FIXED_SAMPLE.id, fact: 'В fixed route literal указан /v1/report, а owner интеграции не задан.', evidenceRef: 'fixed-route-name', unknown: 'Нельзя назначить владельца или выполнить запрос к маршруту.' }, +]); +const FIXED_INTERPRETATIONS = deepFreeze([ + { id: 'synthetic-interpretation-boundary-v1', syntheticOnly: true, observationIds: ['synthetic-observation-header-v1'], dimensionId: 'evidence-boundary', claim: 'Есть основание запросить origin rule; нет основания объяснять stale response конкретной причиной.', uncertainty: 'Один header literal не показывает путь между origin и consumer.' }, + { id: 'synthetic-interpretation-safety-v1', syntheticOnly: true, observationIds: ['synthetic-observation-header-v1', 'synthetic-observation-route-v1'], dimensionId: 'change-safety', claim: 'Следующий шаг должен быть read-only investigation plan с stop condition, а не change.', uncertainty: 'В карточке нет прав, окружения и rollback процедуры.' }, +]); +const FIXED_REVIEWS = deepFreeze([ + { id: 'synthetic-review-boundary-v1', syntheticOnly: true, interpretationIds: ['synthetic-interpretation-boundary-v1'], criterionId: 'evidence-boundary', decision: 'hand-off: request-missing-origin-rule', boundary: 'Не содержит вывода о компетентности или решении о человеке.' }, + { id: 'synthetic-review-safety-v1', syntheticOnly: true, interpretationIds: ['synthetic-interpretation-safety-v1'], criterionId: 'change-safety', decision: 'hand-off: preserve-stop-condition', boundary: 'Не разрешает запрос, код, деплой или production change.' }, +]); +const FIXED_CASES = deepFreeze({ + 'fixed-valid-v1': { role: FIXED_ROLE, sample: FIXED_SAMPLE, observations: FIXED_OBSERVATIONS, interpretations: FIXED_INTERPRETATIONS, reviews: FIXED_REVIEWS }, + 'fixed-memory-test-v1': { role: { ...FIXED_ROLE, excludedDimensions: FIXED_ROLE.excludedDimensions.filter((item) => item !== 'память на термин') }, sample: FIXED_SAMPLE, observations: FIXED_OBSERVATIONS, interpretations: FIXED_INTERPRETATIONS, reviews: FIXED_REVIEWS }, + 'fixed-unsupported-interpretation-v1': { role: FIXED_ROLE, sample: FIXED_SAMPLE, observations: FIXED_OBSERVATIONS, interpretations: [{ ...FIXED_INTERPRETATIONS[0], observationIds: ['missing-observation'] }], reviews: [FIXED_REVIEWS[0]] }, + 'fixed-personal-outcome-v1': { role: FIXED_ROLE, sample: FIXED_SAMPLE, observations: FIXED_OBSERVATIONS, interpretations: FIXED_INTERPRETATIONS, reviews: [{ ...FIXED_REVIEWS[0], decision: 'hiring-recommendation' }] }, + 'fixed-real-evidence-v1': { role: FIXED_ROLE, sample: { ...FIXED_SAMPLE, allowedEvidence: ['fixed-response-header', 'real-repository'] }, observations: FIXED_OBSERVATIONS, interpretations: FIXED_INTERPRETATIONS, reviews: FIXED_REVIEWS }, +}); + +function inspectSyntheticInterview(input) { + if (!input || !Object.values(FIXED_CASES).includes(input)) return deepFreeze({ accepted: false, reasons: ['unrecognised-fixed-input'], handOff: 'stop', productionEffect: 'not-attempted' }); + const reasons = []; + const { role, sample, observations, interpretations, reviews } = input; + if (!hasKeys(role, ROLE_KEYS) || !role.syntheticOnly || !role.excludedDimensions.includes('память на термин') || !role.excludedDimensions.includes('hiring recommendation')) reasons.push('role-rubric-must-exclude-memory-and-outcome'); + if (!hasKeys(sample, SAMPLE_KEYS) || sample.roleId !== role.id || sample.allowedEvidence.some((item) => !item.startsWith('fixed-')) || !sample.forbiddenEvidence.includes('real-repository')) reasons.push('work-sample-boundary-invalid'); + if (!Array.isArray(observations) || observations.some((item) => !hasKeys(item, OBSERVATION_KEYS) || item.sampleId !== sample.id || !sample.allowedEvidence.includes(item.evidenceRef) || !item.unknown)) reasons.push('observation-must-be-fixed-and-unknown-explicit'); + const observationIds = new Set((observations || []).map((item) => item.id)); + if (!Array.isArray(interpretations) || interpretations.some((item) => !hasKeys(item, INTERPRETATION_KEYS) || !item.observationIds.every((id) => observationIds.has(id)) || !role.reviewDimensions.some((dimension) => dimension.id === item.dimensionId) || !item.uncertainty)) reasons.push('interpretation-not-traceable'); + const interpretationIds = new Set((interpretations || []).map((item) => item.id)); + if (!Array.isArray(reviews) || reviews.some((item) => !hasKeys(item, REVIEW_KEYS) || !item.interpretationIds.every((id) => interpretationIds.has(id)) || !role.reviewDimensions.some((dimension) => dimension.id === item.criterionId) || !item.decision.startsWith('hand-off:') || /hiring|person/i.test(item.decision))) reasons.push('review-must-stop-at-synthetic-hand-off'); + return deepFreeze({ scope: MODEL_VERSION, accepted: reasons.length === 0, reasons, handOff: reasons.length ? 'stop-and-repair-boundary' : 'request-missing-evidence-only', productionEffect: 'not-attempted' }); +} + +export function createFixedInterviewInput(caseId) { return FIXED_CASES[caseId] || null; } +export function inspectEngineeringInterview(input) { return inspectSyntheticInterview(input); } +export function prepareSyntheticInterviewHandOff(report) { return deepFreeze({ kind: 'fixed-synthetic-engineering-interview-hand-off-v1', accepted: report?.accepted === true, status: report?.accepted ? 'request-missing-evidence-only' : 'stop-and-repair-boundary', productionEffect: 'not-attempted' }); } +export function runEngineeringInterviewFixture() { + const valid = inspectSyntheticInterview(FIXED_CASES['fixed-valid-v1']); + const memory = inspectSyntheticInterview(FIXED_CASES['fixed-memory-test-v1']); + const unsupported = inspectSyntheticInterview(FIXED_CASES['fixed-unsupported-interpretation-v1']); + const outcome = inspectSyntheticInterview(FIXED_CASES['fixed-personal-outcome-v1']); + const realEvidence = inspectSyntheticInterview(FIXED_CASES['fixed-real-evidence-v1']); + return deepFreeze({ assertions: deepFreeze({ validAccepted: valid.accepted, handOffOnly: prepareSyntheticInterviewHandOff(valid).status === 'request-missing-evidence-only', validNoProduction: valid.productionEffect === 'not-attempted', memoryStops: memory.reasons.includes('role-rubric-must-exclude-memory-and-outcome'), unsupportedStops: unsupported.reasons.includes('interpretation-not-traceable'), outcomeStops: outcome.reasons.includes('review-must-stop-at-synthetic-hand-off'), realEvidenceStops: realEvidence.reasons.includes('work-sample-boundary-invalid'), roleFrozen: Object.isFrozen(FIXED_ROLE), sampleFrozen: Object.isFrozen(FIXED_SAMPLE), observationsFrozen: Object.isFrozen(FIXED_OBSERVATIONS), handOffNoProduction: prepareSyntheticInterviewHandOff(valid).productionEffect === 'not-attempted' }) }); +} + +const practice = revision({ slug: 'editorial-2025-09-practice-engineering-interviews', title: 'Инженерное интервью: рубрика и рабочая проба вместо экзамена по терминам', categories: ['Команда', 'Интервью'], cover: '/assets/editorial/2025/engineering-interviews-2025-rubric-work-sample.svg', excerpt: 'Практический маршрут для технического разговора: ограниченная роль, рабочая проба, наблюдаемые критерии и synthetic hand-off без оценки человека.', readingMinutes: 13 }, [ + p('Инженерный разговор часто ломается ещё до первого вопроса: ведущий разбора (interviewer) спрашивает определение термина, получает уверенный ответ и принимает его за способ работы. Симптом заметен в разборе: нельзя показать, какой факт человек отделил от догадки и где остановился бы без доступа. Цена ошибки — несколько часов команды уходят на спор о впечатлении, а следующая техническая задача снова приходит без проверяемого плана.'), + p('Причина не в том, что терминов стало мало. У разговора нет role rubric — короткого контракта о наблюдаемой работе. Проверка проста: дать одну ограниченную рабочую пробу с неполным входом, заранее записать критерий и запретить вывод, которого evidence не поддерживает. Действие — закончить упражнение только synthetic hand-off, то есть учебным запросом недостающего факта, а не оценкой личности или исходом найма.'), + h2('Рубрика начинается с работы, а не с качеств человека'), + p('Для начала полезно убрать слова «сильный инженер», «подходит команде» и «хорошо рассуждает». Это ярлыки: два reviewer-а вкладывают в них разные наблюдения и не могут восстановить решение через неделю. Рубрика вместо этого называет границу работы. В нашем учебном случае нужно разобрать stale ответ API с тремя fixed literals. Неизвестны правило origin, владелец интеграции и безопасный rollback. Значит, проверяем не память о cache header, а порядок: назвать известное, запросить недостающее, не выдавать change за проверку.'), + figure('/assets/editorial/2025/engineering-interviews-2025-rubric-work-sample.svg', 'Схема synthetic инженерной пробы: role rubric задаёт три наблюдаемых измерения, fixed вход ведёт к observation и unknown, затем reviewer формирует только запрос недостающего доказательства.', 'Рубрика ограничивает работу до того, как начинается разбор. На схеме нет имени, рейтинга или решения о человеке.'), + table('Минимальная role rubric для fixed упражнения', ['Измерение', 'Что можно увидеть', 'Чего нельзя выводить'], [['Граница доказательства', 'Отдельно названы факт, unknown и следующий источник', 'знание всей системы или качество человека'], ['Безопасность изменения', 'Есть read-only проверка и stop condition', 'право выполнять change'], ['Техническая коммуникация', 'Symptom связан с проверкой короткой цепочкой', 'скорость работы в настоящем проекте'], ['Исключено', 'Личность, память терминов, cultural fit', 'любое итоговое решение']]), + h2('Собираем рабочую пробу с ограниченной поверхностью'), + p('Рабочая проба не обязана копировать настоящий сервис и не должна просить доступ к нему. Её задача — оставить ровно столько материала, чтобы виден был маршрут мысли. Поэтому fixed карточка хранит response header, route name и rollback note как литералы в памяти. В ней нет репозитория, сети, логов, времени, аудио или персональных данных. Такой объём нарочно тесный: если для следующего действия нужен внешний факт, хороший результат — остановка и корректный запрос, а не уверенная догадка.'), + p('OPM в датированном руководстве 2008 года связывает вопросы и шкалы с анализом конкретной работы, а не с общим впечатлением. Для технической заметки из этого следует более узкое правило: каждый criterion должен иметь observable form — фразу, артефакт или порядок шагов, который можно показать на одной пробе. Источник не доказывает, что наша рубрика верна для другой роли. Он только поддерживает дисциплину «задача → критерий → наблюдение», которую можно проверить до использования.'), + h2('Воспроизводимый пример: принять только фиксированный вход'), + code(`import { + createFixedInterviewInput, + inspectEngineeringInterview, + prepareSyntheticInterviewHandOff, +} from './upgrade-2025-09.mjs'; + +const input = createFixedInterviewInput('fixed-valid-v1'); +const report = inspectEngineeringInterview(input); + +console.log(report.accepted); // true +console.log(prepareSyntheticInterviewHandOff(report)); +// { status: 'request-missing-evidence-only', productionEffect: 'not-attempted' } +// No person is scored. The input is fixed synthetic data in memory.`), + p('У примера есть намеренно скучное свойство: он не возвращает score. В accepted ветке результатом становится запрос origin rule и сохранённое stop condition. Это проверяемый hand-off между частями упражнения, а не автоматическая оценка. Если заменить fixed literal на реальный репозиторий или попытаться вынести «подходит/не подходит», инспектор должен остановиться. Такой отказ полезнее красивой таблицы баллов: он не даёт технической модели тихо расшириться до обработки реального человека.'), + h2('Как выбрать критерии, которые не дублируют друг друга'), + p('Три критерия в рубрике нужны не для полноты списка, а для трёх разных ошибок. Evidence boundary ловит выдуманную причину: header увидели, а origin rule не видели. Change safety ловит опасный следующий шаг: из симптома сразу делают правку policy. Technical communication ловит потерю связи между symptom и проверкой: вместо маршрута остаётся набор слов про кеш. Если два критерия требуют одного и того же предложения, один из них лишний. Например, «знает HTTP» и «знает Cache-Control» оба заставляют вспоминать термин, но не показывают работу с неопределённостью.'), + p('У каждого критерия полезно записать контрпример. Для границы доказательства контрпример — «max-age=0 значит origin сломан». Для безопасности — «сразу выставим новый TTL». Для коммуникации — «тут сложная кеш-инвалидация». Контрпример не нужен, чтобы поймать человека на ошибке. Он проверяет саму rubric: reviewer заранее видит, какую фразу нельзя принять за evidence. Если контрпример невозможно написать без биографии, интонации или предполагаемых мотивов, criterion надо заменить на наблюдаемую операцию.'), + h2('Порядок одного практического прохода'), + ol(['Назвать work boundary. Одним предложением записать результат упражнения и то, чего оно не делает.', 'Выбрать один symptom. Оставить в карточке только те literals, которые нужны, чтобы сформулировать unknown.', 'Связать criterion с наблюдением. У критерия должна быть наблюдаемая форма, а не оценочное прилагательное.', 'Записать stop condition. Показать, когда дальнейшее действие потребует реального доступа или неподтверждённой причины.', 'Провести review отдельно. Сначала reviewer фиксирует observation, затем interpretation; не смешивает два шага в одну заметку.', 'Собрать synthetic hand-off. Разрешить только запрос недостающего evidence или остановку; не производить hiring outcome.']), + h2('Почему вопрос на термин даёт ложную экономию'), + p('Вопрос «что такое stale-while-revalidate?» дешёв в проведении, но почти не показывает, как человек ограничит изменение, когда header противоречит ожиданию. Можно знать определение и всё равно не спросить, где origin rule, кто владеет интеграцией и можно ли откатить header. Можно не вспомнить термин, но сначала назвать факт, неизвестное и безопасный способ получить следующий сигнал. Поэтому память может быть вспомогательным контекстом, но она исключена из criteria этого учебного упражнения.'), + p('Цена более строгой пробы тоже есть: её нужно собрать, прочитать вслух и проверить на лишние подсказки. Слишком широкий сценарий превращает разговор в проектирование системы; слишком узкий — в угадывание формулировки. Полезный компромисс — одна техническая развилка и явная граница evidence. Если reviewer не может объяснить, зачем в карточке каждый literal, его лучше удалить. Нагрузка уменьшается не сокращением критериев до одного впечатления, а сокращением поверхности до проверяемой задачи.'), + h2('Граница evidence и следующий шаг'), + p('Граница этого практического упражнения жёсткая: role card, входные literals и hand-off существуют только как frozen JavaScript values в памяти. Скрипт не видит человека, не получает аудио или PII, не открывает сеть, файл, репозиторий, CI либо production-систему. Его единственная положительная ветка просит отсутствующий технический факт; ни score, ни record о личности, ни hiring outcome он создать не способен.'), + p('Эта практика не обещает качество интервью, точность процесса или справедливость решения. Она не заменяет требования организации, подготовку reviewer-ов или проверку применимости роли. Её ожидаемый результат уже: после упражнения остаётся цепочка «symptom → observation → unknown → request». Следующий шаг — возьмите одну внутреннюю техническую задачу, удалите реальные данные, оставьте три fixed literals и попросите reviewer-а найти одно место, где interpretation выдана за факт. Если место найдено, рубрика ещё не готова.'), + h2('Историческая граница сентября 2025'), + p('В тексте использованы только публичные источники, опубликованные до 30 сентября 2025, и датированный PDF OPM 2008 года. Они описывают структуру критериев и шкал, но не подтверждают сценарий, политику или результат другой организации. Синтетическая модель ниже этой границы не имитирует реальное собеседование и не создаёт запись о человеке.'), +]); + +const mechanism = revision({ slug: 'editorial-2025-09-mechanism-engineering-interviews', title: 'Инженерное интервью: как не перепутать наблюдение, интерпретацию и решение', categories: ['Команда', 'Интервью'], cover: '/assets/editorial/2025/engineering-interviews-2025-calibration-matrix.svg', excerpt: 'Механика технического review: доказательство, трактовка и разрешённый hand-off живут в разных записях, чтобы догадка не стала итогом.', readingMinutes: 14 }, [ + p('После рабочей пробы заметка reviewer-а (того, кто читает и сверяет запись) нередко выглядит так: «понимает кеширование, можно продолжать». В ней исчезают исходный symptom, услышанный факт и неизвестное; следующий читатель не отличит наблюдение от мнения. Цена ошибки — команда калибрует не критерии, а память о разговоре, и одинаковая работа получает разные трактовки у разных reviewer-ов.'), + p('Причина — три разных операции пишут в одно поле. Observation отвечает «что было в fixed входе», interpretation — «какой узкий вывод разрешён», review decision — «какое действие допустимо дальше». Проверка: у каждого interpretation есть ссылки на observations и фраза об unknown; у decision есть ссылка на interpretation и запрет на personal outcome. Действие — сделать эти записи отдельными объектами и принимать только synthetic hand-off — учебный запрос недостающего факта.'), + h2('Три слоя работают как границы модуля'), + p('Можно представить заметку как маленькую систему с типами. Observation не объясняет, почему stale ответ появился: она сохраняет, что в header задан max-age=0. Interpretation вправе сказать лишь «нужен origin rule», потому что это следует из наблюдения. Review decision не создаёт изменение в сервисе: он просит недостающий rule. Как только слой перепрыгивает границу, цепочка становится непроверяемой. Фраза «не стал бы рисковать» может быть полезной для беседы, но без наблюдаемого условия она не является evidence.'), + figure('/assets/editorial/2025/engineering-interviews-2025-calibration-matrix.svg', 'Матрица калибровки для synthetic review: строки разделяют observation, interpretation и hand-off, столбцы показывают допустимый факт, вопрос и стоп-сигнал; личностный вывод исключён.', 'Схема показывает, что agreement достигается на форме доказательства, а не на общем впечатлении.'), + table('Контракт записей в review', ['Слой', 'Допустимое содержимое', 'Обязательная связь', 'Стоп-сигнал'], [['Observation', 'Факт fixed literal и unknown', 'sample id, evidence ref', 'появились реальный доступ или личные сведения'], ['Interpretation', 'Узкий claim о следующем факте', 'список observation id', 'claim объясняет причину без evidence'], ['Review decision', 'request или stop', 'список interpretation id', 'решение о человеке, change или score'], ['Calibration note', 'расхождение по criterion', 'одна и та же цепочка id', 'переписывание исходного observation']]), + h2('Наблюдение должно пережить несогласие'), + p('Хорошее observation можно перечитать, даже если reviewer не согласен с выводом. «Route задан как /v1/report; owner не указан» переживает спор. «Не разобрался с ownership» уже содержит трактовку и предполагает, что другой стороне был доступен способ разобраться. Поэтому у observation есть поле unknown. Оно не делает заметку слабой; оно фиксирует стоимость следующего шага. До тех пор пока origin rule отсутствует, объяснение stale ответа должно оставаться за границей записи.'), + p('Эта дисциплина особенно важна там, где технический язык маскирует догадку. Слова «architecture smell», «сложный edge case» или «не хватает системного мышления» могут звучать точно, но без симптома и свидетельства они ничего не передают следующему reviewer-у. Лучше сохранить один header literal, чем абзац профессиональных эпитетов. Поздний автор не делает вид, что видит больше данных: он называет, какие данные нужны, и оставляет простой путь проверки.'), + h2('Кодовая проверка traceability'), + code(`import { + createFixedInterviewInput, + inspectEngineeringInterview, +} from './upgrade-2025-09.mjs'; + +const broken = createFixedInterviewInput('fixed-unsupported-interpretation-v1'); +const report = inspectEngineeringInterview(broken); + +console.log(report.accepted); // false +console.log(report.reasons); // ['interpretation-not-traceable'] +// The module stops before a hand-off; it never contacts a service or rates a person.`), + p('Negative path здесь важнее happy path. Он показывает, что interpretation без существующего observation id не получает право перейти в review. Технически это похоже на ссылочную целостность: объект может быть оформлен красиво, но ссылка на отсутствующий источник делает его недействительным. В настоящем процессе эту проверку можно делать вручную по таблице. Скрипт нужен только для того, чтобы фиксированные правила упражнения можно было повторить без сети, файлов и скрытого состояния.'), + h2('Что не следует нормализовать в записи'), + p('Удобная, но опасная привычка — переписать observation так, чтобы reviewer-ы быстрее согласились. Например, один записал «в header есть max-age=0», другой — «ответ явно stale из-за cache». После нормализации может остаться только вторая формулировка, и обсуждение станет гладким, но неверным: причина вошла в журнал без источника. Поэтому у слоёв разный статус. Fact можно уточнить только ссылкой на тот же literal; interpretation можно заменить, оставив старую как расхождение; hand-off можно сузить до stop. Нельзя редактировать ранний слой ради согласованного позднего вывода.'), + p('Такая строгость помогает и при изменении rubric. Если команда решила добавить criterion «владелец следующего шага», она не должна задним числом искать owner в старом sample. Правильный вариант — выпустить новую fixed карточку с явным owner literal либо признать, что старый sample этот criterion не проверяет. Версионирование здесь не бюрократия: оно не даёт сравнению разных заданий выглядеть как несогласие reviewer-ов. В этом пакете модель имеет version id именно для такого разделения, хотя сама не хранит историю и не читает внешнее состояние.'), + h2('Калибровка начинается до обсуждения'), + p('Калибровка не означает, что два reviewer-а обязаны одинаково думать. Она отвечает на более узкий вопрос: одинаково ли они видят criterion и его evidence boundary. До совместного обсуждения каждый читает один fixed sample, выписывает observations и привязывает interpretation к id. Затем сравнивают не итоговую фразу, а первую расходящуюся строку. Если один считает header достаточной причиной, а другой — только симптомом, предмет разговора уже найден: missing origin rule, а не «кто строже».'), + ol(['Заморозить sample. Оба reviewer-а используют одни literals и один stop condition.', 'Писать observation отдельно. Не обсуждать результат, пока не заполнены факт, evidence ref и unknown.', 'Привязать interpretation. Для каждой трактовки показать observation id и criterion id.', 'Сравнить первую развилку. Найти строку, где выводы расходятся, а не усреднять впечатления.', 'Проверить boundary. Удалить всё, что требует личности, реальных данных или незаданного доступа.', 'Сформировать hand-off. Оставить request missing evidence либо stop-and-repair-boundary.']), + h2('Почему общая шкала без evidence не спасает'), + p('Общая шкала полезна, когда её якоря описывают наблюдаемую работу. Но сама цифра не объясняет, какие слова, действия или ограничения увидел reviewer. В учебной модели мы вообще не используем numeric score: это уменьшает соблазн превратить упражнение в ранжирование. Вместо него есть criterion id и decision, который нельзя читать как рекомендацию. Такой выбор не доказывает, что числа всегда вредны; он подходит именно для цели пакета — проверить разделение слоёв на synthetic данных.'), + p('OPM guide 2008 описывает common rating scale и индивидуальные оценки до обсуждения расхождений. Из этого не следует, что любой технический процесс обязан воспроизводить федеральную процедуру. Здесь взят только переносимый механизм: договориться о критерии до review и сохранять основание для разногласия. Наши поля observation/interpretation/decision — авторская инженерная схема, не цитата и не нормативная модель. Она должна быть проверена на собственных ограниченных примерах, прежде чем использоваться где-либо ещё.'), + h2('Ограничение механизма'), + p('Механизм ограничен fixed synthetic records: все ids, facts, claims и decisions заранее заданы в модуле и не переживают процесс выполнения. У него нет сетевого транспорта, файлового ввода, clock, telemetry, Git, CI или production side effect. В нём отсутствуют реальные собеседования, люди и их данные. Поэтому accepted report означает только целостность ссылок между учебными объектами, а не достоверность характеристики кого-либо.'), + p('Разделение записей не устраняет ошибку формулировки role rubric и не даёт полного контекста реальной инженерной работы. Оно также не измеряет скорость, продуктивность, знания или свойства личности. Зато его легко проверить: откройте любую interpretation, удалите мысленно её ссылки на observation и спросите, остаётся ли она доказуемой. Если да, в цепочке, вероятно, спрятан общий ярлык. Следующий шаг — переписать его как request к отсутствующему факту или как явное unknown.'), + h2('Историческая граница сентября 2025'), + p('Источники в разделе ниже публичны и относятся к периоду до 30 сентября 2025; датированный guide OPM выпущен в 2008 году. Они дают контекст для стандартизированных критериев и примеров ответов. Они не подтверждают качество этой модели, не распространяются автоматически на другую организацию и не легитимируют решение о реальном человеке.'), +]); + +const field = revision({ slug: 'editorial-2025-09-field-engineering-interviews', title: 'Инженерное интервью: калибровка reviewer-ов без итоговой оценки человека', categories: ['Команда', 'Интервью'], cover: '/assets/editorial/2025/engineering-interviews-2025-evidence-review-loop.svg', excerpt: 'Полевой технический протокол для одной synthetic пробы: одинаковый вход, независимый review, журнал расхождений и безопасный evidence hand-off.', readingMinutes: 14 }, [ + p('Два reviewer-а (проверяющих записи) читают один технический ответ и за пять минут получают противоположные выводы: один видит осторожный plan, другой — недостаток глубины. В большинстве заметок нельзя восстановить, на какой строке они разошлись. Цена ошибки — calibration превращается в спор авторитетов, а следующий разбор зависит от того, кто говорил громче, а не от одинакового критерия.'), + p('Причина — калибровку проводят после того, как появился итоговый ярлык. Нужен другой порядок: одинаковая synthetic проба, независимые observations, сравнение первой развилки и только затем ограниченный hand-off — учебный запрос недостающего факта. Проверка — каждый review record содержит criterion, ссылку на interpretation и boundary. Действие — завести короткий журнал расхождений, который разрешает запрос evidence, но не создаёт score, ranking или hiring promise.'), + h2('Полевой маршрут начинается с условия остановки'), + p('В этом материале «полевой» не значит реальный разговор. Это проверка процесса на fixed карточке, которую можно повторить локально. Карточка описывает stale API response и намеренно не даёт origin rule. Первый reviewer может назвать header симптомом, второй — поводом немедленно менять cache policy. Разница полезна только если оба записали observation и unknown. Если же один из них пишет «недостаточно системно мыслит», калибровать уже нечего: это не технический факт и не применимый критерий.'), + figure('/assets/editorial/2025/engineering-interviews-2025-evidence-review-loop.svg', 'Замкнутый цикл synthetic review: fixed sample проходит независимые observation записи, затем interpretation с ссылками, журнал расхождения и hand-off для запроса недостающего evidence; стрелка stop блокирует любой личный или production outcome.', 'Цикл не повышает статус результата: он возвращает неопределённость в следующий проверяемый запрос.'), + table('Журнал расхождений для одного fixed sample', ['Шаг', 'Reviewer A', 'Reviewer B', 'Что проверяем вместе'], [['Observation', 'max-age=0 указан', 'route /v1/report указан', 'оба ли факта имеют evidence ref'], ['Unknown', 'нет origin rule', 'нет owner интеграции', 'не заменён ли unknown догадкой'], ['Interpretation', 'запросить rule', 'сначала определить owner', 'какой запрос следует из доступного fact'], ['Hand-off', 'request missing rule', 'request missing owner', 'не появился ли change или вывод о человеке']]), + h2('Независимый review защищает порядок, а не истину'), + p('Независимость нужна, чтобы заметить расхождение до согласования слов. Reviewer A и B не обязаны писать одинаковые interpretation. Они обязаны оставить путь, по которому разницу можно обсудить. Если оба используют один criterion — evidence boundary, но один считает header причиной, видно точное место ошибки: header не содержит origin rule. Если они используют разные criteria, это уже вопрос к rubric. В обоих случаях дискуссия становится короче, потому что объект обсуждения лежит в записи, а не в памяти о беседе.'), + p('Важно не превращать calibration в поиск «правильного reviewer-а». У роли может быть неудачная формулировка, у sample — лишняя подсказка, у criterion — слишком широкая observable form. Журнал расхождений позволяет исправить именно эти артефакты. Он не разрешает приписать причину человеку, который взаимодействовал бы с заданием. В synthetic пакете людей нет вообще; поэтому любое поле, которое требует личного объяснения, означает ошибку модели, а не полезный сигнал.'), + h2('Воспроизводимый пример: остановить personal outcome'), + code(`import { + createFixedInterviewInput, + inspectEngineeringInterview, +} from './upgrade-2025-09.mjs'; + +const unsafe = createFixedInterviewInput('fixed-personal-outcome-v1'); +const report = inspectEngineeringInterview(unsafe); + +console.log(report.accepted); // false +console.log(report.handOff); // 'stop-and-repair-boundary' +// The rejected decision is never converted into a score, record, network call or file.`), + p('Эта ветка специально проверяет не техническое знание, а предел процесса. В records разрешён только текст, начинающийся с hand-off:; «hiring-recommendation» отклоняется. Приём не обещает защитить любой реальный процесс — он проще: делает запрещённое расширение заметным в fixed fixture. Если такой же boundary нужен в живой работе, его должны отдельно определить компетентные владельцы процесса. Код не подменяет этот шаг и не сохраняет данные за пределами памяти.'), + h2('Как менять rubric после расхождения'), + p('После калибровки хочется исправить всё сразу: добавить новые вопросы, больше критериев и подробную шкалу. Это увеличивает стоимость следующего прохода и скрывает, что именно было неясно. В synthetic журнале изменение начинается с классификации расхождения. Если reviewer-ы увидели разные facts, надо поправить sample. Если facts совпали, но interpretation не связана с criterion, надо уточнить rubric. Если interpretation совпала, а hand-off различается, надо поправить допустимые решения. Один тип расхождения — одно изменение артефакта. Так следующий прогон показывает, уменьшилась ли именно прежняя развилка.'), + p('Полезно сохранять старую карточку рядом с новой как учебный контрпример, но не сравнивать их как результаты людей. Смена route literal, stop condition или наблюдаемого критерия меняет задачу. Поэтому калибровка проверяет устойчивость процесса на одном замороженном входе, а не строит статистику между разными входами. Здесь нет чисел намеренно: без заранее определённого метода они создадут видимость точности. Для автора M8 честнее назвать стоимость обновления rubric и оставить воспроизводимый change note, чем объявить процесс измеренным.'), + h2('Как провести короткую calibration session'), + ol(['Проверить synthetic boundary. Убедиться, что sample содержит только fixed literals и исключает реальный доступ.', 'Раздать role rubric. Прочитать work boundary, observable criteria и excluded dimensions до просмотра sample.', 'Собрать независимые записи. Каждый reviewer создаёт observation, unknown и interpretation без общего обсуждения.', 'Сопоставить ссылки. Сравнить sample id, evidence ref и criterion id; найти первую непарную строку.', 'Обсудить artifact, не человека. Исправить rubric, sample или трактовку только через явную границу evidence.', 'Выпустить безопасный hand-off. Зафиксировать request недостающего факта либо stop; ничего не выполнять.']), + h2('Цена калибровки и способ удержать её маленькой'), + p('Калибровка требует времени двух reviewer-ов, а значит её нельзя назначать на каждую фразу. Дешёвый вариант — раз в изменении rubric прогнать один fixed sample и сохранить только первую разницу. Не нужно собирать досье, запись разговора или статистику о людях. Если расхождений нет, журнал содержит только ссылку на fixture и boundary. Если разница есть, меняется один артефакт: criterion, instruction или sample. Так расходы остаются видимыми, а процесс не разрастается в самостоятельную систему наблюдения.'), + p('Рабочая проба тоже имеет цену: хороший вариант требует обновления, когда техническая задача перестаёт быть похожей на реальную работу. Writing Samples Summary OPM отмечает типичность задания и одинаковые условия review как свойства такого метода. Это не лицензия копировать чужую практику и не гарантия того, что короткая проба охватывает работу инженера. Для нашей модели достаточно более скромного критерия: sample должен проверять один способ обращения с неизвестным и явно сообщать, чего он не показывает.'), + h2('Граница доказательства в полевом журнале'), + p('Полевой журнал хранится только как fixed literals в памяти запущенного примера. Он не собирает звук, текст реального разговора, PII, файлы, время, telemetry или данные из сети; также в нём нет Git, CI, запроса к сервису и production effect. Любая ветка, которая пытается добавить реальный evidence или персональный итог, заканчивается stop-and-repair-boundary. Это техническая защита учебного hand-off, а не средство оценивания.'), + p('Ни одна строка журнала не является выводом о способности, потенциале или найме. Нет реального кандидата, интервью, PII, аудио, файлов, сети, telemetry, часов, Git, CI и production effect. «Полевой» здесь означает только повторяемую проверку процесса на литералах. Ожидаемый результат — reviewer может открыть decision и увидеть, какой факт нужен дальше. Следующий шаг: добавьте в свой учебный пример столбец boundary; если его нельзя заполнить без имени или внешнего доступа, sample надо сократить.'), + h2('Историческая граница сентября 2025'), + p('Ни один источник здесь не новее 30 сентября 2025. OPM guide и публичные PDF-материалы описывают структурированные вопросы, критерии и примеры рабочей пробы. Они не задают юридические, HR или hiring обязательства, не валидируют конкретную рубрику и не доказывают корректность решения организации. Этот текст использует их как ограниченный контекст, а не как обещание результата.'), +]); + +export const revisions = [practice, mechanism, field].map(({ proseLength, ...item }) => item); +if (process.argv.includes('--verify-fixture')) { + const assertions = runEngineeringInterviewFixture().assertions; + const failed = Object.entries(assertions).filter(([, value]) => value !== true).map(([key]) => key); + if (failed.length) { process.stderr.write('FAIL fixture: ' + failed.join(', ') + '\n'); process.exitCode = 1; } + else process.stdout.write('PASS fixture: ' + Object.keys(assertions).length + '/' + Object.keys(assertions).length + ' assertions\n'); +} +if (process.argv.includes('--print-revisions')) process.stdout.write(JSON.stringify(revisions) + '\n');