Инженер ускоряет экран и видит рост conversion на графике. Через несколько дней оказывается, что поменялся не выбор пользователя, а способ считать открытие: часть повторных попыток исчезла из denominator. Цена ошибки — выпуск решения по красивому числу, повторное расследование и потеря времени у продукта, аналитики и разработки.
Причина в разорванной цепочке. Latency, click и confirm часто живут в разных отчётах, а failure остаётся техническим alert. Проверка начинается не с нового dashboard: нужно назвать одно решение, один вход в воронку, одно правило attribution и один guardrail. Действие — записать их в contract до того, как смотреть delta.
От технического изменения к пользовательскому решению
Для примера возьмём synthetic изменение: экран подтверждения получает предзагруженную форму. Гипотеза не звучит как «latency важен». Она звучит уже: «если форма открывается без дополнительного ожидания, больше пользователей, которые открыли checkout, доходят до confirm; при этом render failures не должны стать чаще». Здесь loading step — механизм, opened и confirmed — наблюдаемые точки, а failure — цена, которую нельзя спрятать за conversion.
| Звено | Что фиксируем | Симптом ошибки | Действие |
|---|---|---|---|
| Изменение | variant и owner | невозможно сказать, что сравнивали | остановить карточку |
| Открытие | checkout_opened, subject, period | denominator плавает | считать unique subjects |
| Подтверждение | checkout_confirmed + requestId | шаг нельзя отнести к попытке | записать attribution rule |
| Guardrail | render_failed / opened | conversion растёт вместе с отказами | hold и расследовать |
Граница модели и терминов
В статье «событие» — записанный факт с именем, субъектом, cohort, периодом и контекстом. Cohort — группа, которой показан один вариант. Attribution — правило, по которому действие относят к этому варианту. Guardrail — отдельная метрика, которая не даёт купить рост локального показателя деградацией рядом. Это не словарь для дашборда: каждое поле должно отвечать на вопрос, почему изменение разрешили или остановили.
OpenTelemetry в закреплённой версии требует уникальное имя события и документированную структуру. Для продуктовых событий это полезная дисциплина: product.checkout_confirmed не содержит динамический id, а subject, requestId, cohort и period лежат отдельно. Но соглашение об имени не доказывает связь с решением пользователя. Эту связь сначала формулирует владелец продукта и проверяет инженерный контракт.
Один fixed synthetic пример
import { createFixedProductMetricInput, inspectFixedProductMetric, prepareFixedProductDecision } from './upgrade-2025-07.mjs';
const report = inspectFixedProductMetric(
createFixedProductMetricInput('fixed-valid-v1'),
);
const decision = prepareFixedProductDecision(report);
console.log({ accepted: report.accepted, cohorts: report.byCohort, status: decision.status });
// Fixed literals only: no HTTP, file, telemetry, clock or production action.
В наборе четыре открытия, два подтверждения и одно synthetic render failure. Модель считает unique subjects, а не число строк: один повторный event не должен незаметно увеличить denominator. У treatment один из двух открывших подтвердил действие; у control подтверждает один из двух. Это не результат реального продукта и не статистический вывод: небольшой fixed набор нужен только для проверки того, что contract сохраняет cohort, period, denominator и guardrail рядом.
| Поле | Пример | Проверка | Нельзя заключать |
|---|---|---|---|
| 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 |
Общий маршрут до решения
- Назвать решение. Не «смотрим conversion», а «оставляем вариант только если подтверждение не растёт ценой failure».
- Записать причинную цепочку. Техническое изменение → видимый шаг → действие пользователя → outcome и отдельный guardrail.
- Зафиксировать cohort, период и denominator. Эти поля пишут до подсчёта, иначе результат подстраивают под удобный ответ.
- Проверить negative path. Нехватка attribution, иной period или иной denominator должны остановить decision, а не превратиться в null на графике.
- Сохранить decision record. Указать owner, evidence, ограничения и следующий запуск; human owner, а не функция, принимает product decision.
Все события, варианты, результаты и решения ниже — fixed synthetic JS literals. Модуль не читает файлы, сеть, clock, telemetry, Git, CI, production или user data и не выполняет side effect.
Что считать, а что не выдавать за доказательство
Microsoft Research различает local feature metrics, более общие success metrics и guardrail. Это хороший ориентир для раскладки, но не готовая формула для любого продукта. В нашем примере confirm/opened — local metric: он показывает, добрался ли пользователь до целевого шага. Он не равен долгосрочному outcome и не доказывает, что предзагрузка стала причиной. Поэтому decision record хранит формулировку гипотезы и отдельно пишет: «нужна проверка design и достаточного наблюдения».
Если attribution отсутствует, функция возвращает missing-attribution-rule. Это важнее нулевого графика: нельзя понять, относится ли confirm к показанному варианту, другому устройству или повторной попытке. Следующий шаг не «взять больше строк», а зафиксировать ключ связи и проверить, что он есть у обоих событий. Только после этого имеет смысл обсуждать latency или текст кнопки.
Ограничение и следующий шаг
Эта практика не заменяет randomized experiment, расчёт мощности, privacy review или реальный ownership. Fixed literals не показывают статистическую значимость и не содержат денег, команд или production telemetry. Следующий шаг: выберите одну воронку, выпишите denominator в виде множества субъектов и добавьте test, где confirmation пришёл без requestId. Ожидаемый результат — hold с понятной причиной, а не искусственный рост conversion.
Историческая граница июля 2025
Использованы OpenTelemetry v1.31.0 от 11 марта 2025 и работы Microsoft Research 2017 и 2019 годов. Они существовали до 31 июля 2025. Из них взяты только дисциплина события, различение metric roles и риск разного набора наблюдений. Конкретная causal chain, поля и stop decisions — инженерная конструкция этой статьи, не нормативное следствие источников.
Проверяемые источники
- OpenTelemetry Semantic Conventions v1.31.0, events (immutable commit c01aa89, 11 March 2025) — версия: tag v1.31.0; immutable commit c01aa89d9a13042e56536c60975139c50e764796, 2025-03-11. Событие имеет уникальное имя; структура события и применимые attributes должны быть документированы, а dynamic values не должны становиться частью имени. Граница: Это соглашение о семантике telemetry. Оно не определяет продуктовую метрику, causal effect, выбор denominator или критерий ship.
- Microsoft Research: Safe Velocity with Controlled Rollouts (ICSE-SEIP 2019) — версия: ICSE-SEIP 2019 author version, published 2019; HTTPS checked 2026-07-31. Работа различает local feature, success и guardrail metrics; guardrail нужен, чтобы выпуск не ухудшал важные показатели, а local metric помогает объяснить движение более общей метрики. Граница: Это описание controlled rollouts Microsoft. Оно не доказывает, что любое изменение local metric вызвало пользовательский результат в другом продукте.
- Microsoft Research: A Dirty Dozen Metric Interpretation Pitfalls (KDD 2017) — версия: KDD 2017 author version, published 2017; HTTPS checked 2026-07-31. Работа показывает, что разные наборы наблюдений в treatment и control делают metric delta недостоверной; рекомендует разбирать numerator и denominator и отслеживать telemetry loss. Граница: Пример относится к controlled experiments. Он не заменяет дизайн эксперимента, расчёт мощности, privacy policy или проверку конкретного pipeline.