На 31 июля 2026 года строгий аудит проходит 265 из 358 созданных материалов. Остальные 93 не считаются «почти готовыми»: их нужно заменить, а не косметически удлинить.
На 31 июля 2026 года строгий аудит проходит 268 из 358 созданных материалов. Остальные 90 не считаются «почти готовыми»: их нужно заменить, а не косметически удлинить.
- <code>editorial-2025-07-practice-product-metrics</code> — «Метрики продукта для инженера: построить цепочку, а не поднять conversion».
- <code>editorial-2025-07-mechanism-product-metrics</code> — «Почему рост метрики не является решением: denominator, cohort и guardrail».
- <code>editorial-2025-07-field-product-metrics</code> — «Полевой журнал продуктовой метрики: evidence до решения».
Единственный исполняемый артефакт — <code>web/scripts/upgrade-2025-07.mjs</code>. Все events, cohorts, subjects, requestId, периоды, ratios, решения и результаты — fixed synthetic in-memory JS literals. Скрипт не читает и не пишет files, Git, network, CI, clock, telemetry, production или user data и не выполняет side effect. Пакет не подключает overlay к registry, не меняет README, очередь, <code>articles.json</code>, app-страницы, docs, staging, commit или push.
## Исследование и историческая граница
Проверка источников выполнена 31.07.2026 обычными HTTPS-запросами без авторизации и TLS bypass. Историческая граница — 31 июля 2025: OpenTelemetry закреплён immutable commit, работы Microsoft Research опубликованы в 2017 и 2019 годах.
| Источник | Закрепление | Узко подтверждаемое утверждение | Граница |
| --- | --- | --- | --- |
| [OpenTelemetry events](https://raw.githubusercontent.com/open-telemetry/semantic-conventions/c01aa89d9a13042e56536c60975139c50e764796/docs/general/events.md) | tag <code>v1.31.0</code>, commit <code>c01aa89d9a13042e56536c60975139c50e764796</code>, 11.03.2025 | event имеет уникальное имя; convention документирует его структуру и attributes; dynamic values не включаются в event name. | Не определяет продуктовую causal effect, denominator, guardrail или ship criterion. |
| [Safe Velocity with Controlled Rollouts](https://www.microsoft.com/en-us/research/uploads/prod/2020/06/Safe-Velocity-ICSE-SEI.pdf) | ICSE-SEIP 2019, author version | local feature, success и guardrail metrics имеют разные роли; guardrail не должен деградировать при rollout. | Описывает controlled rollouts Microsoft, не доказывает эффект произвольной local metric в другом продукте. |
| [A Dirty Dozen Metric Interpretation Pitfalls](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/2017-08-KDDMetricInterpretationPitfalls.pdf) | KDD 2017, author version | другой состав наблюдений и telemetry loss могут исказить metric delta; numerator и denominator полезно разбирать отдельно. | Не заменяет дизайн конкретного эксперимента, power analysis, privacy policy или pipeline validation. |
Причинная цепочка, названия полей, synthetic input и правило HOLD — собственная инженерная конструкция статей. Они не приписаны источникам как framework guarantee.
## Проход 1 — факты, техника и источники
- <code>createFixedProductMetricInput</code> выдаёт только один из четырёх frozen fixed cases. <code>inspectFixedProductMetric</code> считает unique subjects для opened, confirmed и render_failed; не принимает произвольный input как доказательство.
- Успешный case требует attribution, declared denominator и единственный cohort-period. <code>prepareFixedProductDecision</code> после PASS создаёт только <code>needs-human-owner-decision</code>; production effect остаётся <code>not-attempted</code>.
- Negative paths проверены отдельно: нет attribution → <code>missing-attribution-rule</code>; другой период → <code>mixed-cohort-or-period</code>; <code>all-events</code> вместо unique opened subjects → <code>wrong-denominator</code>.
- Из источников в тексте взяты только документированная форма события, разные роли local/success/guardrail и риск неверного состава наблюдений. Нет утверждения, что рост confirm сам доказывает причинность или что конкретный threshold применим вне synthetic example.
**Вердикт прохода 1:** пройдено. Код и текст разделяют зафиксированный учебный contract, human decision и реальные продуктовые выводы.
## Проход 2 — голос, плотность, ситуация и цена
| Статья | Первые два абзаца | Основной текст | Артефакт | Проверяемый финал |
| --- | --- | ---: | --- | --- |
| practice | Изолированный рост conversion и цена false release/повторного расследования | 6 648 | causal funnel, event contract, fixture | test на confirm без requestId возвращает HOLD |
| mechanism | Разный состав numerator/denominator и цена false positive | 6 410 | matrix, ratio contract, negative cases | событие из другого period останавливает расчёт |
| field | Число отделено от context и цена неповторимого decision | 6 291 | decision record, evidence loop, stop example | другой инженер восстанавливает contract по карточке |
Три текста соответствуют голосу М8: автор не продаёт «рост метрик», а сравнивает риск, owner и воспроизводимый путь. Термины раскрыты в момент первого использования: cohort, attribution, denominator, guardrail, local metric и decision record. Формула «симптом → причина → проверка → действие» сохраняется в каждом разделе. Синтетические числа не выдаются за telemetry, реальные деньги, людей или инциденты.
**Вердикт прохода 2:** пройдено. Усиленный <code>audit:draft</code> подтвердил проблему и цену ошибки в первых двух абзацах, диапазон 5–15 тыс. знаков, таблицу, figure, code example, ordered sequence и источники.
## Проход 3 — SVG, fixture и выпуск
Последовательность фактически выполненных проверок:
- <code>cd web && npm run audit:draft -- scripts/upgrade-2025-07.mjs</code> — PASS: **6 648 / 6 410 / 6 291** body chars после независимой редакторской правки.
- <code>xmllint --noout</code> для трёх SVG — PASS; safety scan чист: нет <code>script</code>, <code>foreignObject</code>, <code>javascript:</code>, <code>data:image</code> и event-handler attributes.
- Sharp renders на 375 px просмотрены вручную. Funnel сохраняет отдельную красную ветку guardrail; matrix читается в четыре строки; evidence loop показывает HOLD ветку и не обрезает подписи.
| Asset | Смысл | Результат 375 px |
| --- | --- | --- |
| <code>product-metrics-2025-causal-funnel.svg</code> | change → opened → confirmed → human review и guardrail | PASS: стрелки, подписи шагов и красный guardrail видимы. |
| <code>product-metrics-2025-metric-guardrail-matrix.svg</code> | четыре измерительных условия и HOLD | PASS: контраст и строки читаемы, нижняя оговорка не обрезана. |
| <code>product-metrics-2025-decision-evidence-loop.svg</code> | evidence loop с веткой repair contract | PASS: пять шагов и HOLD branch различимы на узком экране. |
Full build и <code>audit:articles</code> намеренно не запускались: overlay не подключён к registry. Их должен выполнить независимый интегратор после подключения. Commit и публикация не выполнялись.
**Вердикт прохода 3:** draft-пакет готов к независимой приёмке, но не опубликован.
## Выпуск после независимой приёмки
### 1. Факты и техника
Редактор проверил источники заново. Immutable OpenTelemetry commit `c01aa89d9a13042e56536c60975139c50e764796` от 11 марта 2025 подтверждает уникальное имя события, документированную структуру и низкую кардинальность имени. PDF Safe Velocity (ICSE-SEIP 2019) подтверждает разные роли data-quality, guardrail, success и local metrics; PDF Dirty Dozen (KDD 2017) подтверждает, что sample/telemetry mismatch может сделать delta недостоверной и требует разбирать numerator и denominator. Эти факты не используются как доказательство causal effect или ship decision для synthetic набора.
Заново выполнены `node --check`, fixture 11/11 и усиленный draft audit. Все три code examples выполнены вручную: valid case заканчивается `needs-human-owner-decision`, wrong denominator и mixed period — `stop-before-product-decision` с точной причиной. Скрипт сохраняет границу: фиксированные literals в памяти, без запроса dashboard, telemetry или production action.
### 2. Редактура и голос
Полное чтение выявило чрезмерный общий вводный блок у mechanism и field. Он удалён и заменён на два специальных раздела: проверку ratio до интерпретации и чтение карточки решения. Поэтому статьи не пересказывают practice, а раскрывают собственный вопрос: доказательство измерения и воспроизводимое decision record. После правки основной текст составляет 6 648, 6 410 и 6 291 знак; в первых двух абзацах каждой названы проблема и цена ошибки. Термины раскрыты в контексте, а причинность оставлена проверяемой гипотезой, не обещанием фреймворка.
### 3. Визуал и выпуск
Три SVG прошли XML и safety scan. Независимый Sharp render на 375 px подтвердил, что causal funnel, metric matrix и evidence loop читаются без обрезания; у каждой figure есть содержательный alt и подпись. После подключения overlay целевой audit подтвердил: practice — одна figure, две таблицы и один example; mechanism и field — по одной figure, таблице и примеру. Registry содержит 259 уникальных ревизий, `git diff --check` чист, production build успешно сгенерировал 374 страницы.
<titleid="title">Причинная воронка product metric</title><descid="desc">Техническое изменение ведет к открытию и подтверждению checkout; failure отображен как независимый guardrail.</desc>
<rectwidth="960"height="540"fill="#101827"/><textx="60"y="70"fill="#f8fafc"font-family="Arial"font-size="30"font-weight="700">Одна цепочка до решения</text>
<gfont-family="Arial"font-size="20"text-anchor="middle"><rectx="55"y="190"width="190"height="100"rx="16"fill="#1d4ed8"/><textx="150"y="230"fill="white">изменение</text><textx="150"y="260"fill="#dbeafe">форма без ожидания</text><rectx="300"y="190"width="190"height="100"rx="16"fill="#0369a1"/><textx="395"y="230"fill="white">opened</text><textx="395"y="260"fill="#cffafe">unique subject</text><rectx="545"y="190"width="190"height="100"rx="16"fill="#0f766e"/><textx="640"y="230"fill="white">confirmed</text><textx="640"y="260"fill="#ccfbf1">same request</text><rectx="790"y="190"width="130"height="100"rx="16"fill="#047857"/><textx="855"y="230"fill="white">review</text><textx="855"y="260"fill="#d1fae5">human</text></g>
<rectx="640"y="370"width="235"height="95"rx="16"fill="#b91c1c"/><textx="757"y="410"fill="white"font-family="Arial"font-size="22"text-anchor="middle">guardrail</text><textx="757"y="440"fill="#fee2e2"font-family="Arial"font-size="18"text-anchor="middle">render_failed / opened</text><textx="60"y="500"fill="#cbd5e1"font-family="Arial"font-size="18">Сначала contract и одинаковый period; затем metric; затем решение.</text>
<svgxmlns="http://www.w3.org/2000/svg"width="960"height="560"viewBox="0 0 960 560"role="img"aria-labelledby="title desc"><titleid="title">Цикл evidence для решения</title><descid="desc">Пять шагов от гипотезы к human decision, с отдельной красной веткой HOLD для нарушенного contract.</desc><rectwidth="960"height="560"fill="#101827"/><textx="55"y="65"fill="#f8fafc"font-family="Arial"font-size="30"font-weight="700">Evidence loop: остановка тоже результат</text><gfont-family="Arial"text-anchor="middle"font-size="18"><gfill="#075985"><rectx="65"y="200"width="145"height="82"rx="14"/><textx="137"y="234"fill="white">hypothesis</text><textx="137"y="260"fill="#cffafe">change → user step</text><rectx="245"y="200"width="145"height="82"rx="14"/><textx="317"y="234"fill="white">event contract</text><textx="317"y="260"fill="#cffafe">name + fields</text><rectx="425"y="200"width="145"height="82"rx="14"/><textx="497"y="234"fill="white">metric check</text><textx="497"y="260"fill="#cffafe">period + denominator</text><rectx="605"y="200"width="145"height="82"rx="14"/><textx="677"y="234"fill="white">guardrail</text><textx="677"y="260"fill="#cffafe">degradation?</text><rectx="785"y="200"width="120"height="82"rx="14"fill="#047857"/><textx="845"y="234"fill="white">human</text><textx="845"y="260"fill="#d1fae5">decision</text></g><gstroke="#94a3b8"stroke-width="5"><pathd="M210 241h28"/><pathd="M390 241h28"/><pathd="M570 241h28"/><pathd="M750 241h28"/></g><pathd="M497 285v90h100"stroke="#f87171"stroke-width="5"fill="none"/><rectx="600"y="340"width="250"height="80"rx="14"fill="#991b1b"/><textx="725"y="373"fill="white">HOLD: repair contract</text><textx="725"y="400"fill="#fee2e2">missing attribution / mixed period</text></g><textx="55"y="500"fill="#cbd5e1"font-family="Arial"font-size="18">Decision record хранит evidence и limitation; функция не делает выпуск автоматически.</text></svg>
<svgxmlns="http://www.w3.org/2000/svg"width="960"height="580"viewBox="0 0 960 580"role="img"aria-labelledby="title desc"><titleid="title">Матрица metric и guardrail</title><descid="desc">Таблица отделяет допустимый сигнал от трёх условий, которые ведут к остановке решения.</desc><rectwidth="960"height="580"fill="#101827"/><textx="55"y="65"fill="#f8fafc"font-family="Arial"font-size="30"font-weight="700">Metric без contract не допускает решение</text><gfont-family="Arial"font-size="19"><rectx="55"y="105"width="850"height="58"fill="#334155"/><textx="85"y="142"fill="white">Проверка</text><textx="410"y="142"fill="white">Сигнал</text><textx="695"y="142"fill="white">Результат</text><gfill="#e2e8f0"><rectx="55"y="163"width="850"height="72"fill="#172554"/><textx="85"y="207">attribution</text><textx="410"y="207">subject + requestId</text><textx="695"y="207">иначе HOLD</text><rectx="55"y="235"width="850"height="72"fill="#0f172a"/><textx="85"y="279">denominator</text><textx="410"y="279">unique opened subject</text><textx="695"y="279">иначе HOLD</text><rectx="55"y="307"width="850"height="72"fill="#172554"/><textx="85"y="351">cohort и period</text><textx="410"y="351">одно окно для вариантов</text><textx="695"y="351">иначе HOLD</text><rectx="55"y="379"width="850"height="72"fill="#0f172a"/><textx="85"y="423">guardrail</text><textx="410"y="423">render_failed / opened</text><textx="695"y="423">human review</text></g></g><rectx="55"y="488"width="850"height="46"rx="12"fill="#7f1d1d"/><textx="480"y="519"fill="#fee2e2"font-family="Arial"font-size="19"text-anchor="middle">Local movement — не ship; сначала проверка измерительного контракта.</text></svg>
claim:'Событие имеет уникальное имя; структура события и применимые attributes должны быть документированы, а dynamic values не должны становиться частью имени.',
boundary:'Это соглашение о семантике telemetry. Оно не определяет продуктовую метрику, causal effect, выбор denominator или критерий ship.',
},
{
title:'Microsoft Research: Safe Velocity with Controlled Rollouts (ICSE-SEIP 2019)',
version:'ICSE-SEIP 2019 author version, published 2019; HTTPS checked 2026-07-31',
claim:'Работа различает local feature, success и guardrail metrics; guardrail нужен, чтобы выпуск не ухудшал важные показатели, а local metric помогает объяснить движение более общей метрики.',
boundary:'Это описание controlled rollouts Microsoft. Оно не доказывает, что любое изменение local metric вызвало пользовательский результат в другом продукте.',
},
{
title:'Microsoft Research: A Dirty Dozen Metric Interpretation Pitfalls (KDD 2017)',
version:'KDD 2017 author version, published 2017; HTTPS checked 2026-07-31',
claim:'Работа показывает, что разные наборы наблюдений в treatment и control делают metric delta недостоверной; рекомендует разбирать numerator и denominator и отслеживать telemetry loss.',
boundary:'Пример относится к controlled experiments. Он не заменяет дизайн эксперимента, расчёт мощности, privacy policy или проверку конкретного pipeline.',
constSYNTHETIC_BOUNDARY='Все события, варианты, результаты и решения ниже — fixed synthetic JS literals. Модуль не читает файлы, сеть, clock, telemetry, Git, CI, production или user data и не выполняет side effect.';
p('В статье «событие» — записанный факт с именем, субъектом, cohort, периодом и контекстом. Cohort — группа, которой показан один вариант. Attribution — правило, по которому действие относят к этому варианту. Guardrail — отдельная метрика, которая не даёт купить рост локального показателя деградацией рядом. Это не словарь для дашборда: каждое поле должно отвечать на вопрос, почему изменение разрешили или остановили.'),
p('OpenTelemetry в закреплённой версии требует уникальное имя события и документированную структуру. Для продуктовых событий это полезная дисциплина: <code>product.checkout_confirmed</code> не содержит динамический id, а <code>subject</code>, <code>requestId</code>, <code>cohort</code> и <code>period</code> лежат отдельно. Но соглашение об имени не доказывает связь с решением пользователя. Эту связь сначала формулирует владелец продукта и проверяет инженерный контракт.'),
h2('Один fixed synthetic пример'),
code("import { createFixedProductMetricInput, inspectFixedProductMetric, prepareFixedProductDecision } from './upgrade-2025-07.mjs';\n\nconst report = inspectFixedProductMetric(\n createFixedProductMetricInput('fixed-valid-v1'),\n);\nconst decision = prepareFixedProductDecision(report);\n\nconsole.log({ accepted: report.accepted, cohorts: report.byCohort, status: decision.status });\n// Fixed literals only: no HTTP, file, telemetry, clock or production action."),
p('В наборе четыре открытия, два подтверждения и одно synthetic render failure. Модель считает unique subjects, а не число строк: один повторный event не должен незаметно увеличить denominator. У treatment один из двух открывших подтвердил действие; у control подтверждает один из двух. Это не результат реального продукта и не статистический вывод: небольшой fixed набор нужен только для проверки того, что contract сохраняет cohort, period, denominator и guardrail рядом.'),
table('Контракт события и роль поля',['Поле','Пример','Проверка','Нельзя заключать'],[['event','product.checkout_opened','имя описывает тип факта','что пользователь доволен'],['subject','u-b','deduplicate denominator','что это реальная личность'],['cohort','treatment','сравнить один вариант с control','что варианты случайно распределены'],['period','2025-07-14','не смешать окна','что эффект сохранится завтра'],['requestId','r-2','связать шаги одной попытки','что причина уже доказана'],['errorType','synthetic-timeout','посчитать guardrail','что это production incident']]),
h2('Общий маршрут до решения'),
ol(['<strong>Назвать решение.</strong> Не «смотрим conversion», а «оставляем вариант только если подтверждение не растёт ценой failure».','<strong>Записать причинную цепочку.</strong> Техническое изменение → видимый шаг → действие пользователя → outcome и отдельный guardrail.','<strong>Зафиксировать cohort, период и denominator.</strong> Эти поля пишут до подсчёта, иначе результат подстраивают под удобный ответ.','<strong>Проверить negative path.</strong> Нехватка attribution, иной period или иной denominator должны остановить decision, а не превратиться в null на графике.','<strong>Сохранить decision record.</strong> Указать owner, evidence, ограничения и следующий запуск; human owner, а не функция, принимает product decision.']),
p(SYNTHETIC_BOUNDARY),
];
constpractice=revision({slug:'editorial-2025-07-practice-product-metrics',title:'Метрики продукта для инженера: построить цепочку, а не поднять conversion',categories:['Метрики','Инженерия'],cover:'/assets/editorial/2025/product-metrics-2025-causal-funnel.svg',excerpt:'Практический маршрут: связать изменение, событие, действие пользователя, guardrail и решение так, чтобы ошибка измерения останавливалась раньше релиза.',readingMinutes:12},[
p('Инженер ускоряет экран и видит рост conversion на графике. Через несколько дней оказывается, что поменялся не выбор пользователя, а способ считать открытие: часть повторных попыток исчезла из denominator. Цена ошибки — выпуск решения по красивому числу, повторное расследование и потеря времени у продукта, аналитики и разработки.'),
p('Причина в разорванной цепочке. Latency, click и confirm часто живут в разных отчётах, а failure остаётся техническим alert. Проверка начинается не с нового dashboard: нужно назвать одно решение, один вход в воронку, одно правило attribution и один guardrail. Действие — записать их в contract до того, как смотреть delta.'),
h2('От технического изменения к пользовательскому решению'),
p('Для примера возьмём synthetic изменение: экран подтверждения получает предзагруженную форму. Гипотеза не звучит как «latency важен». Она звучит уже: «если форма открывается без дополнительного ожидания, больше пользователей, которые открыли checkout, доходят до confirm; при этом render failures не должны стать чаще». Здесь loading step — механизм, opened и confirmed — наблюдаемые точки, а failure — цена, которую нельзя спрятать за conversion.'),
figure('/assets/editorial/2025/product-metrics-2025-causal-funnel.svg','Причинная воронка fixed synthetic примера: техническое изменение ведёт к открытию checkout, подтверждению и решению пользователя; сбоку отдельно проходит guardrail render failure. Каждое ребро подписано событием и границей интерпретации.','Воронка не утверждает причинность сама по себе. Она заставляет до измерения указать, какой шаг наблюдаем, где attribution и какое ухудшение останавливает выпуск.'),
table('Цепочка и проверка',['Звено','Что фиксируем','Симптом ошибки','Действие'],[['Изменение','variant и owner','невозможно сказать, что сравнивали','остановить карточку'],['Открытие','checkout_opened, subject, period','denominator плавает','считать unique subjects'],['Подтверждение','checkout_confirmed + requestId','шаг нельзя отнести к попытке','записать attribution rule'],['Guardrail','render_failed / opened','conversion растёт вместе с отказами','hold и расследовать']]),
...common,
h2('Что считать, а что не выдавать за доказательство'),
p('Microsoft Research различает local feature metrics, более общие success metrics и guardrail. Это хороший ориентир для раскладки, но не готовая формула для любого продукта. В нашем примере confirm/opened — local metric: он показывает, добрался ли пользователь до целевого шага. Он не равен долгосрочному outcome и не доказывает, что предзагрузка стала причиной. Поэтому decision record хранит формулировку гипотезы и отдельно пишет: «нужна проверка design и достаточного наблюдения».'),
p('Если attribution отсутствует, функция возвращает <code>missing-attribution-rule</code>. Это важнее нулевого графика: нельзя понять, относится ли confirm к показанному варианту, другому устройству или повторной попытке. Следующий шаг не «взять больше строк», а зафиксировать ключ связи и проверить, что он есть у обоих событий. Только после этого имеет смысл обсуждать latency или текст кнопки.'),
h2('Ограничение и следующий шаг'),
p('Эта практика не заменяет randomized experiment, расчёт мощности, privacy review или реальный ownership. Fixed literals не показывают статистическую значимость и не содержат денег, команд или production telemetry. Следующий шаг: выберите одну воронку, выпишите denominator в виде множества субъектов и добавьте test, где confirmation пришёл без requestId. Ожидаемый результат — hold с понятной причиной, а не искусственный рост conversion.'),
h2('Историческая граница июля 2025'),
p('Использованы OpenTelemetry v1.31.0 от 11 марта 2025 и работы Microsoft Research 2017 и 2019 годов. Они существовали до 31 июля 2025. Из них взяты только дисциплина события, различение metric roles и риск разного набора наблюдений. Конкретная causal chain, поля и stop decisions — инженерная конструкция этой статьи, не нормативное следствие источников.'),
]);
constmechanism=revision({slug:'editorial-2025-07-mechanism-product-metrics',title:'Почему рост метрики не является решением: denominator, cohort и guardrail',categories:['Метрики','Качество'],cover:'/assets/editorial/2025/product-metrics-2025-metric-guardrail-matrix.svg',excerpt:'Механика проверки: отделить movement локальной метрики от допустимого решения и остановить расчёт при смешанных периодах, неверном denominator или отсутствии attribution.',readingMinutes:13},[
p('На отчёте treatment выглядит лучше control, но numerator и denominator собраны из разных наборов. Иногда это случается после невинной оптимизации: страница перестаёт перезагружаться, клиентский event теряется или cohort получает другое окно. Цена ошибки — false positive: команда объясняет пользователю эффект, которого не было, и выпускает изменение с неизвестным ущербом.'),
p('Причина не в самой дроби. Любая ratio скрывает состав числителя и знаменателя. Проверка — разложить metric на набор субъектов, события и период, затем убедиться, что treatment и control отвечают на один и тот же вопрос. Действие — сделать эти условия частью result; если хотя бы одно не выполнено, решение получает stop status.'),
h2('Metric — это контракт, а не имя на графике'),
p('В fixed model conversion задан как unique subjects с <code>checkout_confirmed</code>, делённое на unique subjects с <code>checkout_opened</code>, внутри одного cohort и одного дня. Такой contract намеренно мал. Он не говорит, сколько ждать пользователя, как обрабатывать два устройства и что считать «успешным заказом». Но он не даёт случайно заменить denominator на <code>all-events</code> и выдать плотность telemetry за поведение людей.'),
figure('/assets/editorial/2025/product-metrics-2025-metric-guardrail-matrix.svg','Матрица fixed synthetic расчёта: local metric, success interpretation и guardrail разделены; строки missing attribution, wrong denominator и mixed period ведут к HOLD, а не к ship.','Матрица показывает границу: удачный local signal разрешает только human review, а любое нарушение измерительного контракта останавливает product decision.'),
table('Negative paths, которые нельзя сгладить',['Сбой','Почему delta нельзя читать','Машинный result','Ремонт'],[['Нет attribution','confirm не связан с вариантом','missing-attribution-rule','добавить ключ связи и test'],['Смешан period','группы не наблюдались в одном окне','mixed-cohort-or-period','зафиксировать окно до запроса'],['Неверный denominator','ratio отвечает на другой вопрос','wrong-denominator','считать unique opened subjects'],['Guardrail деградирует','локальный выигрыш куплен отказами','eligible, но human hold','разобрать error path']]),
h2('Проверить ratio до интерпретации'),
p('Numerator — число подтверждений, denominator — число открывших, но оба значения имеют смысл только вместе с правилом inclusion. Cohort здесь означает вариант сравнения, attribution связывает подтверждение с попыткой, а guardrail описывает недопустимое ухудшение рядом. Эти термины не доказывают причинность: они лишь делают видимыми условия, без которых ratio отвечает на другой вопрос.'),
code("import { createFixedProductMetricInput, inspectFixedProductMetric, prepareFixedProductDecision } from './upgrade-2025-07.mjs';\n\nconst report = inspectFixedProductMetric(\n createFixedProductMetricInput('fixed-wrong-denominator-v1'),\n);\nconst decision = prepareFixedProductDecision(report);\n\nconsole.log({ accepted: report.accepted, reasons: report.reasons, status: decision.status });\n// Expected: wrong-denominator and stop-before-product-decision.\n// The input is fixed synthetic data in memory; no metric is sent or published."),
p('Этот пример не вычисляет «правильную conversion» задним числом. Он показывает, что имя метрики не переносится на другой denominator. Report с <code>all-events</code> получает stop до human decision, хотя набор событий внешне похож на valid case. Для реального расчёта отдельно нужны randomization, задержка доставки, политика идентификаторов и проверка pipeline.'),
h2('Порядок проверки сравнения'),
ol(['<strong>Назвать decision.</strong> Сформулировать, какое действие будет разрешено или остановлено, а не просто открыть dashboard.','<strong>Зафиксировать population.</strong> До запроса записать cohort, period, inclusion и attribution.','<strong>Разложить дробь.</strong> Показать numerator и denominator отдельно и проверить, что они относятся к одной задаче.','<strong>Положить рядом guardrail.</strong> Сравнить его по тому же окну и назвать владельца порога.','<strong>Вернуть HOLD при разрыве.</strong> Repair contract предшествует product discussion.']),
p(SYNTHETIC_BOUNDARY),
h2('Почему guardrail живёт рядом, а не в конце отчёта'),
p('Guardrail не обязан быть «самой важной» метрикой. Его работа проще: заранее назвать ухудшение, которое делает локальный выигрыш недостаточным. В synthetic наборе один render_failed у двух opened в control не создаёт сравнения для реального продукта. Он показывает контракт: failure имеет тот же cohort и тот же denominator base, поэтому его нельзя потерять при смене панели. В реальном проекте порог и owner определяются до запуска, а не после удачного результата.'),
p('Работа Safe Velocity описывает guardrail как показатель, который не должен ухудшаться при rollout, и local metric как средство объяснить изменения. Нельзя перевернуть эту фразу в гарантию: если local metric вырос, значит outcome улучшился. Между ними остаются выборка, вариант, сезонность, качество событий и другие механизмы. Поэтому автор оставляет человеческое решение последним шагом и требует доказательства, достаточного для конкретного риска.'),
h2('Разбор numerator и denominator'),
p('Работа Microsoft о metric pitfalls особенно полезна именно здесь: она показывает пример, где другой состав наблюдений делает delta ненадёжной, и предлагает декомпозицию numerator и denominator. Наше практическое следствие скромнее: вместе с числом хранить список правил, из которого оно получено. Если после изменения request path исчезла часть opened, alert должен говорить о denominator, а не радоваться conversion.'),
p('Проверка в модуле добавляет один confirm из следующего дня. Результат <code>mixed-cohort-or-period</code> останавливает расчёт до сравнения. Это не статистический тест и не защита от всех ошибок: код не проверяет рандомизацию, задержку доставки или личность subject. Зато он делает распространённую подмену видимой и не позволяет default-фильтру quietly собрать удобную выборку.'),
h2('Ограничение и следующий шаг'),
p('Не делайте из guardrail универсальный список. У одной операции критичен отказ, у другой — отмена, доступность или безопасность; метрика получает смысл только рядом с пользовательским решением и владельцем риска. Следующий шаг: возьмите одну ratio из своего dashboard, напишите её numerator и denominator словами, затем подайте в test события из другого периода. Ожидаемый результат — расчёт остановлен с причиной, которую может исправить конкретный owner.'),
h2('Историческая граница июля 2025'),
p('Microsoft Research PDF 2017 года использован для риска sample/metric mismatch, а работа 2019 года — для ролей local, success и guardrail. OpenTelemetry v1.31.0 закреплён commit от марта 2025. Они не доказывают причинность fixed example и не задают пороги. Структура decision record и её stop reasons — ограниченный проектный выбор автора.'),
]);
constfield=revision({slug:'editorial-2025-07-field-product-metrics',title:'Полевой журнал продуктовой метрики: evidence до решения',categories:['Метрики','Процессы'],cover:'/assets/editorial/2025/product-metrics-2025-decision-evidence-loop.svg',excerpt:'Полевой формат decision record: собрать цепочку, metric definition, guardrail, negative path и ограничение так, чтобы владелец мог принять или остановить решение.',readingMinutes:13},[
p('Самая дорогая ошибка в продуктовой метрике часто происходит не в SQL и не в графике. В review остаётся фраза «conversion вырос», но не остаётся периода, cohort, definition и guardrail. Через неделю никто не может повторить расчёт или объяснить, почему выпуск состоялся. Цена ошибки — повторная работа, спор об owner и решение, которое нельзя защитить evidence.'),
p('Причина — результат отделён от решения. Dashboard хранит число, а контекст живёт в сообщениях и памяти участников. Проверка — собрать короткий decision record до discussion: изменение, causal hypothesis, event contract, denominator, cohort/period, guardrail, evidence и limitation. Действие — отправить на human decision только карточку без red flags; остальные остановить и вернуть владельцу измерения.'),
h2('Карточка решения должна помещаться в review'),
table('Fixed synthetic decision record',['Поле','Значение','Вопрос reviewer'],[['Decision','leave / hold после human review','какой риск принимаем?'],['Hypothesis','форма без ожидания помогает пройти confirm','какое звено наблюдаем?'],['Attribution','same subject and request','как confirm связан с вариантом?'],['Window','2025-07-14','одинаков ли период cohort?'],['Local metric','confirmed unique subjects / opened unique subjects','каков denominator?'],['Guardrail','render_failed / opened','что останавливает выпуск?'],['Limitation','fixed synthetic only','чего evidence не доказывает?']]),
figure('/assets/editorial/2025/product-metrics-2025-decision-evidence-loop.svg','Цикл evidence для product decision: определить change и hypothesis, зафиксировать event contract, проверить cohort-period-denominator и guardrail, затем создать human decision record либо HOLD с причиной ремонта.','Decision loop специально заканчивается человеком и ограничением. Рисунок не превращает один metric delta в автоматический ship.'),
h2('Карточка хранит условия решения, а не красивый итог'),
p('Cohort — это группа с одним вариантом, attribution — правило связи попытки и подтверждения, denominator — множество открывших, guardrail — отдельное ограничение на ущерб. В field record они нужны не для терминологической полноты, а чтобы reviewer увидел, какое поле надо вернуть владельцу измерения. Если отсутствует хотя бы одно, фраза «conversion вырос» не становится решением.'),
p('Здесь нет настоящего пользователя, записи сессии или telemetry: subject и requestId — fixed synthetic labels. Они показывают место для связи, но не советуют хранить реальный идентификатор. Модуль создаёт только decision hand-off или HOLD; он не запрашивает dashboard и не выпускает вариант.'),
h2('Как выглядит полезный negative path'),
p('В этой теме остановка — результат, а не исключение. Если <code>checkout_confirmed</code> пришёл без requestId, отчёт не должен гадать, к какому открытию его отнести. Если в treatment попал следующий день, отчёт не должен выровнять его средним. Если denominator стал <code>all-events</code>, расчёт не должен сохранять прежнее название conversion. Каждая ветка возвращает короткую причину и владельца ремонта: instrumentation owner, query owner или experiment owner.'),
code("import { createFixedProductMetricInput, inspectFixedProductMetric, prepareFixedProductDecision } from './upgrade-2025-07.mjs';\n\nconst report = inspectFixedProductMetric(\n createFixedProductMetricInput('fixed-mixed-period-v1'),\n);\nconsole.log(prepareFixedProductDecision(report));\n// Expected: stop-before-product-decision with mixed-cohort-or-period.\n// The input is fixed in-memory synthetic data; nothing is published or sent."),
p('Такой путь полезен на review: вместо «данных мало» он говорит, какое именно условие нарушено. Вызов не исправляет cohort и не меняет production. Он только не разрешает перейти к решению. Это важно для ответственности: владелец интерфейса не обязан чинить SQL, а владелец metric contract не обязан объяснять UX-гипотезу. Карточка связывает их по явному полю, а не по догадке.'),
h2('Порядок полевой проверки'),
ol(['<strong>Сначала прочитать решение.</strong> Если нельзя закончить фразой «оставить или остановить вариант при таких условиях», метрика ещё не выбрана.','<strong>Затем проверить событие.</strong> Имя, subject, cohort, period и ключ attribution должны быть записаны до query.','<strong>Разложить ratio.</strong> Отдельно вывести unique numerator и unique denominator; не подменять субъект строкой telemetry.','<strong>Посмотреть guardrail тем же срезом.</strong> Если у него другой период или population, назвать ограничение, не сравнивать молча.','<strong>Прогнать один отрицательный вход.</strong> Missing attribution, mixed period или wrong denominator обязан привести к HOLD.','<strong>Зафиксировать evidence.</strong> Ссылка на definition, запрос, owner и следующий check делают результат воспроизводимым.']),
h2('Что делать, когда local metric двигается'),
p('Сначала не объявлять успех. Сверить, что метрика использует тот же event schema и что набор субъектов не менялся. Затем показать guardrail и diagnostic split: отдельно cohorts, period и numerator/denominator. Только после этого reviewer решает, нужно ли расширять наблюдение, повторять запуск или менять реализацию. Этот порядок короче бесконечного dashboard tour, потому что каждый шаг отвечает на отдельный риск.'),
p('Статья о metric pitfalls предупреждает, что другой состав выборок может исказить направление delta, а потеря telemetry может быть одним из источников bias. Это подтверждает необходимость проверки, но не создаёт готовый pipeline. В конкретном проекте нужно отдельно проверить privacy, retention, идентификаторы и задержку событий. Synthetic requestId здесь лишь показывает место связи; он не является советом хранить реальный идентификатор пользователя.'),
h2('Ограничение и следующий проверяемый шаг'),
p('Полевой журнал не обещает, что команда научится доказывать все product effects. Он делает неизвестное явным: какую связь мы наблюдаем, какое ухудшение не принимаем и чего не хватает для решения. В этом пакете нет реальных пользователей, денег, инцидентов, SQL, сети или production query. Поэтому его fixture проверяет только deterministic stop branches и отсутствие side effect.'),
p('Следующий шаг: добавьте к одному существующему review ровно четыре строки — decision, denominator, guardrail и limitation. Затем попросите другого инженера восстановить query только по карточке. Если он не может понять cohort или period, не улучшайте визуализацию: сначала исправьте event contract. Ожидаемый результат — спор о выпуске превращается в список проверяемых условий.'),
h2('Историческая граница июля 2025'),
p('Источники ограничены материалами, существовавшими до 31 июля 2025: Microsoft Research 2017/2019 и OpenTelemetry v1.31.0. Их утверждения отделены от нашего процесса: они поддерживают ценность раздельных metric roles, документированного события и проверки состава выборки. Формат журнала, synthetic literals и решение HOLD — практическая схема автора, а не обещание framework.'),
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.